1 /*
2  * Copyright 2011-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.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 /**
46  * Simple value object to capture information of {@link PersistentEntity}s.
47  *
48  * @author Oliver Gierke
49  * @author Jon Brisbin
50  * @author Patryk Wasik
51  * @author Thomas Darimont
52  * @author Christoph Strobl
53  * @author Mark Paluch
54  */

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     /**
81      * Creates a new {@link BasicPersistentEntity} from the given {@link TypeInformation}.
82      *
83      * @param information must not be {@literal null}.
84      */

85     public BasicPersistentEntity(TypeInformation<T> information) {
86         this(information, null);
87     }
88
89     /**
90      * Creates a new {@link BasicPersistentEntity} for the given {@link TypeInformation} and {@link Comparator}. The given
91      * {@link Comparator} will be used to define the order of the {@link PersistentProperty} instances added to the
92      * entity.
93      *
94      * @param information must not be {@literal null}.
95      * @param comparator can be {@literal null}.
96      */

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     /*
124      * (non-Javadoc)
125      * @see org.springframework.data.mapping.PersistentEntity#getPersistenceConstructor()
126      */

127     @Nullable
128     public PreferredConstructor<T, P> getPersistenceConstructor() {
129         return constructor;
130     }
131
132     /*
133      * (non-Javadoc)
134      * @see org.springframework.data.mapping.PersistentEntity#isConstructorArgument(org.springframework.data.mapping.PersistentProperty)
135      */

136     public boolean isConstructorArgument(PersistentProperty<?> property) {
137         return constructor != null && constructor.isConstructorParameter(property);
138     }
139
140     /*
141      * (non-Javadoc)
142      * @see org.springframework.data.mapping.PersistentEntity#isIdProperty(org.springframework.data.mapping.PersistentProperty)
143      */

144     public boolean isIdProperty(PersistentProperty<?> property) {
145         return idProperty != null && idProperty.equals(property);
146     }
147
148     /*
149      * (non-Javadoc)
150      * @see org.springframework.data.mapping.PersistentEntity#isVersionProperty(org.springframework.data.mapping.PersistentProperty)
151      */

152     public boolean isVersionProperty(PersistentProperty<?> property) {
153         return versionProperty != null && versionProperty.equals(property);
154     }
155
156     /*
157      * (non-Javadoc)
158      * @see org.springframework.data.mapping.PersistentEntity#getName()
159      */

160     public String getName() {
161         return getType().getName();
162     }
163
164     /*
165      * (non-Javadoc)
166      * @see org.springframework.data.mapping.PersistentEntity#getIdProperty()
167      */

168     @Nullable
169     public P getIdProperty() {
170         return idProperty;
171     }
172
173     /*
174      * (non-Javadoc)
175      * @see org.springframework.data.mapping.PersistentEntity#getVersionProperty()
176      */

177     @Nullable
178     public P getVersionProperty() {
179         return versionProperty;
180     }
181
182     /*
183      * (non-Javadoc)
184      * @see org.springframework.data.mapping.PersistentEntity#hasIdProperty()
185      */

186     public boolean hasIdProperty() {
187         return idProperty != null;
188     }
189
190     /*
191      * (non-Javadoc)
192      * @see org.springframework.data.mapping.PersistentEntity#hasVersionProperty()
193      */

194     public boolean hasVersionProperty() {
195         return versionProperty != null;
196     }
197
198     /*
199      * (non-Javadoc)
200      * @see org.springframework.data.mapping.model.MutablePersistentEntity#addPersistentProperty(P)
201      */

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     /*
242      * (non-Javadoc)
243      * @see org.springframework.data.mapping.model.MutablePersistentEntity#setEvaluationContextProvider(org.springframework.data.spel.EvaluationContextProvider)
244      */

245     @Override
246     public void setEvaluationContextProvider(EvaluationContextProvider provider) {
247         this.evaluationContextProvider = provider;
248     }
249
250     /**
251      * Returns the given property if it is a better candidate for the id property than the current id property.
252      *
253      * @param property the new id property candidate, will never be {@literal null}.
254      * @return the given id property or {@literal nullif the given property is not an id property.
255      */

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     /*
274      * (non-Javadoc)
275      * @see org.springframework.data.mapping.model.MutablePersistentEntity#addAssociation(org.springframework.data.mapping.model.Association)
276      */

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     /*
285      * (non-Javadoc)
286      * @see org.springframework.data.mapping.PersistentEntity#getPersistentProperty(java.lang.String)
287      */

288     @Override
289     @Nullable
290     public P getPersistentProperty(String name) {
291         return propertyCache.get(name);
292     }
293
294     /*
295      * (non-Javadoc)
296      * @see org.springframework.data.mapping.PersistentEntity#getPersistentProperties(java.lang.String)
297      */

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     /*
321      * (non-Javadoc)
322      * @see org.springframework.data.mapping.PersistentEntity#getType()
323      */

324     public Class<T> getType() {
325         return information.getType();
326     }
327
328     /*
329      * (non-Javadoc)
330      * @see org.springframework.data.mapping.PersistentEntity#getTypeAlias()
331      */

332     public Alias getTypeAlias() {
333         return typeAlias.get();
334     }
335
336     /*
337      * (non-Javadoc)
338      * @see org.springframework.data.mapping.PersistentEntity#getTypeInformation()
339      */

340     public TypeInformation<T> getTypeInformation() {
341         return information;
342     }
343
344     /*
345      * (non-Javadoc)
346      * @see org.springframework.data.mapping.PersistentEntity#doWithProperties(org.springframework.data.mapping.PropertyHandler)
347      */

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     /*
358      * (non-Javadoc)
359      * @see org.springframework.data.mapping.PersistentEntity#doWithProperties(org.springframework.data.mapping.PropertyHandler.Simple)
360      */

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     /*
372      * (non-Javadoc)
373      * @see org.springframework.data.mapping.PersistentEntity#doWithAssociations(org.springframework.data.mapping.AssociationHandler)
374      */

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     /*
385      * (non-Javadoc)
386      * @see org.springframework.data.mapping.PersistentEntity#doWithAssociations(org.springframework.data.mapping.SimpleAssociationHandler)
387      */

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     /*
398      * (non-Javadoc)
399      * @see org.springframework.data.mapping.PersistentEntity#findAnnotation(java.lang.Class)
400      */

401     @Nullable
402     @Override
403     public <A extends Annotation> A findAnnotation(Class<A> annotationType) {
404         return doFindAnnotation(annotationType).orElse(null);
405     }
406
407     /*
408      * (non-Javadoc)
409      * @see org.springframework.data.mapping.PersistentEntity#isAnnotationPresent(java.lang.Class)
410      */

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     /*
424      * (non-Javadoc)
425      * @see org.springframework.data.mapping.model.MutablePersistentEntity#verify()
426      */

427     public void verify() {
428
429         if (comparator != null) {
430             properties.sort(comparator);
431             persistentPropertiesCache.sort(comparator);
432         }
433     }
434
435     /*
436      * (non-Javadoc)
437      * @see org.springframework.data.mapping.model.MutablePersistentEntity#setPersistentPropertyAccessorFactory(org.springframework.data.mapping.model.PersistentPropertyAccessorFactory)
438      */

439     @Override
440     public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) {
441         this.propertyAccessorFactory = factory;
442     }
443
444     /*
445      * (non-Javadoc)
446      * @see org.springframework.data.mapping.PersistentEntity#getPropertyAccessor(java.lang.Object)
447      */

448     @Override
449     public <B> PersistentPropertyAccessor<B> getPropertyAccessor(B bean) {
450
451         verifyBeanType(bean);
452
453         return propertyAccessorFactory.getPropertyAccessor(this, bean);
454     }
455
456     /* 
457      * (non-Javadoc)
458      * @see org.springframework.data.mapping.PersistentEntity#getPropertyPathAccessor(java.lang.Object)
459      */

460     @Override
461     public <B> PersistentPropertyPathAccessor<B> getPropertyPathAccessor(B bean) {
462         return new SimplePersistentPropertyPathAccessor<>(getPropertyAccessor(bean));
463     }
464
465     /*
466      * (non-Javadoc)
467      * @see org.springframework.data.mapping.PersistentEntity#getIdentifierAccessor(java.lang.Object)
468      */

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     /*
482      * (non-Javadoc)
483      * @see org.springframework.data.mapping.PersistentEntity#isNew(java.lang.Object)
484      */

485     @Override
486     public boolean isNew(Object bean) {
487
488         verifyBeanType(bean);
489
490         return isNewStrategy.get().isNew(bean);
491     }
492
493     /*
494      * (non-Javadoc)
495      * @see org.springframework.data.mapping.PersistentEntity#isImmutable()
496      */

497     @Override
498     public boolean isImmutable() {
499         return isImmutable.get();
500     }
501
502     /*
503      * (non-Javadoc)
504      * @see org.springframework.data.mapping.PersistentEntity#requiresPropertyPopulation()
505      */

506     @Override
507     public boolean requiresPropertyPopulation() {
508         return requiresPropertyPopulation.get();
509     }
510
511     /*
512      * (non-Javadoc)
513      * @see java.lang.Iterable#iterator()
514      */

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     /**
539      * Returns the default {@link IsNewStrategy} to be used. Will be a {@link PersistentEntityIsNewStrategy} by default.
540      * Note, that this strategy only gets used if the entity doesn't implement {@link Persistable} as this indicates the
541      * user wants to be in control over whether an entity is new or not.
542      *
543      * @return
544      * @since 2.1
545      */

546     protected IsNewStrategy getFallbackIsNewStrategy() {
547         return PersistentEntityIsNewStrategy.of(this);
548     }
549
550     /**
551      * Verifies the given bean type to no be {@literal null} and of the type of the current {@link PersistentEntity}.
552      *
553      * @param bean must not be {@literal null}.
554      */

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     /**
563      * Calculates the {@link Alias} to be used for the given type.
564      *
565      * @param type must not be {@literal null}.
566      * @return
567      */

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     /**
579      * A null-object implementation of {@link IdentifierAccessor} to be able to return an accessor for entities that do
580      * not have an identifier property.
581      *
582      * @author Oliver Gierke
583      */

584     private static class AbsentIdentifierAccessor extends TargetAwareIdentifierAccessor {
585
586         public AbsentIdentifierAccessor(Object target) {
587             super(target);
588         }
589
590         /*
591          * (non-Javadoc)
592          * @see org.springframework.data.mapping.IdentifierAccessor#getIdentifier()
593          */

594         @Override
595         @Nullable
596         public Object getIdentifier() {
597             return null;
598         }
599     }
600
601     /**
602      * Simple {@link Comparator} adaptor to delegate ordering to the inverse properties of the association.
603      *
604      * @author Oliver Gierke
605      */

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         /*
614          * (non-Javadoc)
615          * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
616          */

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