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.context;
17
18 import lombok.AccessLevel;
19 import lombok.NonNull;
20 import lombok.RequiredArgsConstructor;
21
22 import java.beans.PropertyDescriptor;
23 import java.lang.reflect.Field;
24 import java.lang.reflect.Modifier;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Map;
30 import java.util.Optional;
31 import java.util.Set;
32 import java.util.concurrent.locks.Lock;
33 import java.util.concurrent.locks.ReentrantReadWriteLock;
34 import java.util.function.Predicate;
35 import java.util.stream.Collectors;
36
37 import org.springframework.beans.BeanUtils;
38 import org.springframework.beans.BeansException;
39 import org.springframework.beans.factory.InitializingBean;
40 import org.springframework.context.ApplicationContext;
41 import org.springframework.context.ApplicationContextAware;
42 import org.springframework.context.ApplicationEventPublisher;
43 import org.springframework.context.ApplicationEventPublisherAware;
44 import org.springframework.core.KotlinDetector;
45 import org.springframework.data.mapping.MappingException;
46 import org.springframework.data.mapping.PersistentEntity;
47 import org.springframework.data.mapping.PersistentProperty;
48 import org.springframework.data.mapping.PersistentPropertyPath;
49 import org.springframework.data.mapping.PersistentPropertyPaths;
50 import org.springframework.data.mapping.PropertyPath;
51 import org.springframework.data.mapping.model.BeanWrapperPropertyAccessorFactory;
52 import org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory;
53 import org.springframework.data.mapping.model.EntityInstantiators;
54 import org.springframework.data.mapping.model.InstantiationAwarePropertyAccessorFactory;
55 import org.springframework.data.mapping.model.MutablePersistentEntity;
56 import org.springframework.data.mapping.model.PersistentPropertyAccessorFactory;
57 import org.springframework.data.mapping.model.Property;
58 import org.springframework.data.mapping.model.SimpleTypeHolder;
59 import org.springframework.data.spel.EvaluationContextProvider;
60 import org.springframework.data.spel.ExtensionAwareEvaluationContextProvider;
61 import org.springframework.data.util.ClassTypeInformation;
62 import org.springframework.data.util.KotlinReflectionUtils;
63 import org.springframework.data.util.Optionals;
64 import org.springframework.data.util.Streamable;
65 import org.springframework.data.util.TypeInformation;
66 import org.springframework.lang.Nullable;
67 import org.springframework.util.Assert;
68 import org.springframework.util.ReflectionUtils;
69 import org.springframework.util.ReflectionUtils.FieldCallback;
70 import org.springframework.util.ReflectionUtils.FieldFilter;
71
72 /**
73  * Base class to build mapping metadata and thus create instances of {@link PersistentEntity} and
74  * {@link PersistentProperty}.
75  * <p>
76  * The implementation uses a {@link ReentrantReadWriteLock} to make sure {@link PersistentEntity} are completely
77  * populated before accessing them from outside.
78  *
79  * @param <E> the concrete {@link PersistentEntity} type the {@link MappingContext} implementation creates
80  * @param <P> the concrete {@link PersistentProperty} type the {@link MappingContext} implementation creates
81  * @author Jon Brisbin
82  * @author Oliver Gierke
83  * @author Michael Hunger
84  * @author Thomas Darimont
85  * @author Tomasz Wysocki
86  * @author Mark Paluch
87  * @author Mikael Klamra
88  * @author Christoph Strobl
89  */

90 public abstract class AbstractMappingContext<E extends MutablePersistentEntity<?, P>, P extends PersistentProperty<P>>
91         implements MappingContext<E, P>, ApplicationEventPublisherAware, ApplicationContextAware, InitializingBean {
92
93     private static final boolean IN_NATIVE_IMAGE = System.getProperty("org.graalvm.nativeimage.imagecode") != null;
94
95     private final Optional<E> NONE = Optional.empty();
96     private final Map<TypeInformation<?>, Optional<E>> persistentEntities = new HashMap<>();
97     private final PersistentPropertyAccessorFactory persistentPropertyAccessorFactory;
98     private final PersistentPropertyPathFactory<E, P> persistentPropertyPathFactory;
99
100     private @Nullable ApplicationEventPublisher applicationEventPublisher;
101     private EvaluationContextProvider evaluationContextProvider = EvaluationContextProvider.DEFAULT;
102
103     private Set<? extends Class<?>> initialEntitySet = new HashSet<>();
104     private boolean strict = false;
105     private SimpleTypeHolder simpleTypeHolder = SimpleTypeHolder.DEFAULT;
106
107     private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
108     private final Lock read = lock.readLock();
109     private final Lock write = lock.writeLock();
110
111     protected AbstractMappingContext() {
112
113         this.persistentPropertyPathFactory = new PersistentPropertyPathFactory<>(this);
114
115         EntityInstantiators instantiators = new EntityInstantiators();
116         PersistentPropertyAccessorFactory accessorFactory = IN_NATIVE_IMAGE ? BeanWrapperPropertyAccessorFactory.INSTANCE
117                 : new ClassGeneratingPropertyAccessorFactory();
118
119         this.persistentPropertyAccessorFactory = new InstantiationAwarePropertyAccessorFactory(accessorFactory,
120                 instantiators);
121     }
122
123     /*
124      * (non-Javadoc)
125      * @see org.springframework.context.ApplicationEventPublisherAware#setApplicationEventPublisher(org.springframework.context.ApplicationEventPublisher)
126      */

127     @Override
128     public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
129         this.applicationEventPublisher = applicationEventPublisher;
130     }
131
132     /*
133      * (non-Javadoc)
134      * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
135      */

136     @Override
137     public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
138
139         this.evaluationContextProvider = new ExtensionAwareEvaluationContextProvider(applicationContext);
140
141         if (applicationEventPublisher == null) {
142             this.applicationEventPublisher = applicationContext;
143         }
144     }
145
146     /**
147      * Sets the {@link Set} of types to populate the context initially.
148      *
149      * @param initialEntitySet
150      */

151     public void setInitialEntitySet(Set<? extends Class<?>> initialEntitySet) {
152         this.initialEntitySet = initialEntitySet;
153     }
154
155     /**
156      * Configures whether the {@link MappingContext} is in strict mode which means, that it will throw
157      * {@link MappingException}s in case one tries to lookup a {@link PersistentEntity} not already in the context. This
158      * defaults to {@literal false} so that unknown types will be transparently added to the MappingContext if not known
159      * in advance.
160      *
161      * @param strict
162      */

163     public void setStrict(boolean strict) {
164         this.strict = strict;
165     }
166
167     /**
168      * Configures the {@link SimpleTypeHolder} to be used by the {@link MappingContext}. Allows customization of what
169      * types will be regarded as simple types and thus not recursively analyzed.
170      *
171      * @param simpleTypes must not be {@literal null}.
172      */

173     public void setSimpleTypeHolder(SimpleTypeHolder simpleTypes) {
174
175         Assert.notNull(simpleTypes, "SimpleTypeHolder must not be null!");
176
177         this.simpleTypeHolder = simpleTypes;
178     }
179
180     /*
181      * (non-Javadoc)
182      * @see org.springframework.data.mapping.model.MappingContext#getPersistentEntities()
183      */

184     @Override
185     public Collection<E> getPersistentEntities() {
186
187         try {
188
189             read.lock();
190
191             return persistentEntities.values().stream()//
192                     .flatMap(Optionals::toStream)//
193                     .collect(Collectors.toSet());
194
195         } finally {
196             read.unlock();
197         }
198     }
199
200     /*
201      * (non-Javadoc)
202      * @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(java.lang.Class)
203      */

204     @Nullable
205     public E getPersistentEntity(Class<?> type) {
206         return getPersistentEntity(ClassTypeInformation.from(type));
207     }
208
209     /*
210      * (non-Javadoc)
211      * @see org.springframework.data.mapping.context.MappingContext#hasPersistentEntityFor(java.lang.Class)
212      */

213     @Override
214     public boolean hasPersistentEntityFor(Class<?> type) {
215
216         Assert.notNull(type, "Type must not be null!");
217
218         Optional<E> entity = persistentEntities.get(ClassTypeInformation.from(type));
219
220         return entity == null ? false : entity.isPresent();
221     }
222
223     /*
224      * (non-Javadoc)
225      * @see org.springframework.data.mapping.model.MappingContext#getPersistentEntity(org.springframework.data.util.TypeInformation)
226      */

227     @Nullable
228     @Override
229     public E getPersistentEntity(TypeInformation<?> type) {
230
231         Assert.notNull(type, "Type must not be null!");
232
233         try {
234
235             read.lock();
236
237             Optional<E> entity = persistentEntities.get(type);
238
239             if (entity != null) {
240                 return entity.orElse(null);
241             }
242
243         } finally {
244             read.unlock();
245         }
246
247         if (!shouldCreatePersistentEntityFor(type)) {
248
249             try {
250                 write.lock();
251                 persistentEntities.put(type, NONE);
252             } finally {
253                 write.unlock();
254             }
255
256             return null;
257         }
258
259         if (strict) {
260             throw new MappingException("Unknown persistent entity " + type);
261         }
262
263         return addPersistentEntity(type).orElse(null);
264     }
265
266     /*
267      * (non-Javadoc)
268      * @see org.springframework.data.mapping.context.MappingContext#getPersistentEntity(org.springframework.data.mapping.PersistentProperty)
269      */

270     @Nullable
271     @Override
272     public E getPersistentEntity(P persistentProperty) {
273
274         Assert.notNull(persistentProperty, "PersistentProperty must not be null!");
275
276         if (!persistentProperty.isEntity()) {
277             return null;
278         }
279
280         TypeInformation<?> typeInfo = persistentProperty.getTypeInformation();
281         return getPersistentEntity(typeInfo.getRequiredActualType());
282     }
283
284     /*
285      * (non-Javadoc)
286      * @see org.springframework.data.mapping.context.MappingContext#getPersistentPropertyPath(java.lang.Class, java.lang.String)
287      */

288     @Override
289     public PersistentPropertyPath<P> getPersistentPropertyPath(PropertyPath propertyPath) {
290         return persistentPropertyPathFactory.from(propertyPath);
291     }
292
293     /*
294      * (non-Javadoc)
295      * @see org.springframework.data.mapping.context.MappingContext#getPersistentPropertyPath(java.lang.String, java.lang.Class)
296      */

297     @Override
298     public PersistentPropertyPath<P> getPersistentPropertyPath(String propertyPath, Class<?> type) {
299         return persistentPropertyPathFactory.from(type, propertyPath);
300     }
301
302     /*
303      * (non-Javadoc)
304      * @see org.springframework.data.mapping.context.MappingContext#findPersistentPropertyPath(java.lang.Class, java.util.function.Predicate)
305      */

306     @Override
307     public <T> PersistentPropertyPaths<T, P> findPersistentPropertyPaths(Class<T> type, Predicate<? super P> predicate) {
308
309         Assert.notNull(type, "Type must not be null!");
310         Assert.notNull(predicate, "Selection predicate must not be null!");
311
312         return doFindPersistentPropertyPaths(type, predicate, it -> !it.isAssociation());
313     }
314
315     /**
316      * Actually looks up the {@link PersistentPropertyPaths} for the given type, selection predicate and traversal guard.
317      * Primary purpose is to allow sub-types to alter the default traversal guard, e.g. used by
318      * {@link #findPersistentPropertyPaths(Class, Predicate)}.
319      *
320      * @param type will never be {@literal null}.
321      * @param predicate will never be {@literal null}.
322      * @param traversalGuard will never be {@literal null}.
323      * @return will never be {@literal null}.
324      * @see #findPersistentPropertyPaths(Class, Predicate)
325      */

326     protected final <T> PersistentPropertyPaths<T, P> doFindPersistentPropertyPaths(Class<T> type,
327             Predicate<? super P> predicate, Predicate<P> traversalGuard) {
328         return persistentPropertyPathFactory.from(ClassTypeInformation.from(type), predicate, traversalGuard);
329     }
330
331     /**
332      * Adds the given type to the {@link MappingContext}.
333      *
334      * @param type must not be {@literal null}.
335      * @return
336      */

337     protected Optional<E> addPersistentEntity(Class<?> type) {
338         return addPersistentEntity(ClassTypeInformation.from(type));
339     }
340
341     /**
342      * Adds the given {@link TypeInformation} to the {@link MappingContext}.
343      *
344      * @param typeInformation must not be {@literal null}.
345      * @return
346      */

347     protected Optional<E> addPersistentEntity(TypeInformation<?> typeInformation) {
348
349         Assert.notNull(typeInformation, "TypeInformation must not be null!");
350
351         try {
352
353             read.lock();
354
355             Optional<E> persistentEntity = persistentEntities.get(typeInformation);
356
357             if (persistentEntity != null) {
358                 return persistentEntity;
359             }
360
361         } finally {
362             read.unlock();
363         }
364
365         Class<?> type = typeInformation.getType();
366         E entity = null;
367
368         try {
369
370             write.lock();
371
372             entity = createPersistentEntity(typeInformation);
373
374             entity.setEvaluationContextProvider(evaluationContextProvider);
375
376             // Eagerly cache the entity as we might have to find it during recursive lookups.
377             persistentEntities.put(typeInformation, Optional.of(entity));
378
379             PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(type);
380
381             final Map<String, PropertyDescriptor> descriptors = new HashMap<>();
382             for (PropertyDescriptor descriptor : pds) {
383                 descriptors.put(descriptor.getName(), descriptor);
384             }
385
386             try {
387
388                 PersistentPropertyCreator persistentPropertyCreator = new PersistentPropertyCreator(entity, descriptors);
389                 ReflectionUtils.doWithFields(type, persistentPropertyCreator, PersistentPropertyFilter.INSTANCE);
390                 persistentPropertyCreator.addPropertiesForRemainingDescriptors();
391
392                 entity.verify();
393
394                 if (persistentPropertyAccessorFactory.isSupported(entity)) {
395                     entity.setPersistentPropertyAccessorFactory(persistentPropertyAccessorFactory);
396                 }
397
398             } catch (RuntimeException e) {
399                 persistentEntities.remove(typeInformation);
400                 throw e;
401             }
402
403         } catch (BeansException e) {
404             throw new MappingException(e.getMessage(), e);
405         } finally {
406             write.unlock();
407         }
408
409         // Inform listeners
410         if (applicationEventPublisher != null && entity != null) {
411             applicationEventPublisher.publishEvent(new MappingContextEvent<>(this, entity));
412         }
413
414         return Optional.of(entity);
415     }
416
417     /*
418      * (non-Javadoc)
419      * @see org.springframework.data.mapping.context.PersistentEntityAware#getManagedTypes()
420      */

421     @Override
422     public Collection<TypeInformation<?>> getManagedTypes() {
423
424         try {
425
426             read.lock();
427             return Collections.unmodifiableSet(new HashSet<>(persistentEntities.keySet()));
428
429         } finally {
430             read.unlock();
431         }
432     }
433
434     /**
435      * Creates the concrete {@link PersistentEntity} instance.
436      *
437      * @param <T>
438      * @param typeInformation
439      * @return
440      */

441     protected abstract <T> E createPersistentEntity(TypeInformation<T> typeInformation);
442
443     /**
444      * Creates the concrete instance of {@link PersistentProperty}.
445      *
446      * @param property
447      * @param owner
448      * @param simpleTypeHolder
449      * @return
450      */

451     protected abstract P createPersistentProperty(Property property, E owner, SimpleTypeHolder simpleTypeHolder);
452
453     /*
454      * (non-Javadoc)
455      * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
456      */

457     @Override
458     public void afterPropertiesSet() {
459         initialize();
460     }
461
462     /**
463      * Initializes the mapping context. Will add the types configured through {@link #setInitialEntitySet(Set)} to the
464      * context.
465      */

466     public void initialize() {
467         initialEntitySet.forEach(this::addPersistentEntity);
468     }
469
470     /**
471      * Returns whether a {@link PersistentEntity} instance should be created for the given {@link TypeInformation}. By
472      * default this will reject all types considered simple and non-supported Kotlin classes, but it might be necessary to
473      * tweak that in case you have registered custom converters for top level types (which renders them to be considered
474      * simple) but still need meta-information about them.
475      * <p/>
476      *
477      * @param type will never be {@literal null}.
478      * @return
479      */

480     protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
481
482         if (simpleTypeHolder.isSimpleType(type.getType())) {
483             return false;
484         }
485
486         return !KotlinDetector.isKotlinType(type.getType()) || KotlinReflectionUtils.isSupportedKotlinClass(type.getType());
487     }
488
489     /**
490      * {@link FieldCallback} to create {@link PersistentProperty} instances.
491      *
492      * @author Oliver Gierke
493      */

494     @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
495     private final class PersistentPropertyCreator implements FieldCallback {
496
497         private final @NonNull E entity;
498         private final @NonNull Map<String, PropertyDescriptor> descriptors;
499         private final @NonNull Map<String, PropertyDescriptor> remainingDescriptors;
500
501         public PersistentPropertyCreator(E entity, Map<String, PropertyDescriptor> descriptors) {
502             this(entity, descriptors, descriptors);
503         }
504
505         /*
506          * (non-Javadoc)
507          * @see org.springframework.util.ReflectionUtils.FieldCallback#doWith(java.lang.reflect.Field)
508          */

509         public void doWith(Field field) {
510
511             String fieldName = field.getName();
512             TypeInformation<?> type = entity.getTypeInformation();
513
514             ReflectionUtils.makeAccessible(field);
515
516             Property property = Optional.ofNullable(descriptors.get(fieldName))//
517                     .map(it -> Property.of(type, field, it))//
518                     .orElseGet(() -> Property.of(type, field));
519
520             createAndRegisterProperty(property);
521
522             this.remainingDescriptors.remove(fieldName);
523         }
524
525         /**
526          * Adds {@link PersistentProperty} instances for all suitable {@link PropertyDescriptor}s without a backing
527          * {@link Field}.
528          *
529          * @see PersistentPropertyFilter
530          */

531         public void addPropertiesForRemainingDescriptors() {
532
533             remainingDescriptors.values().stream() //
534                     .filter(Property::supportsStandalone) //
535                     .map(it -> Property.of(entity.getTypeInformation(), it)) //
536                     .filter(PersistentPropertyFilter.INSTANCE::matches) //
537                     .forEach(this::createAndRegisterProperty);
538         }
539
540         private void createAndRegisterProperty(Property input) {
541
542             P property = createPersistentProperty(input, entity, simpleTypeHolder);
543
544             if (property.isTransient()) {
545                 return;
546             }
547
548             if (!input.isFieldBacked() && !property.usePropertyAccess()) {
549                 return;
550             }
551
552             entity.addPersistentProperty(property);
553
554             if (property.isAssociation()) {
555                 entity.addAssociation(property.getRequiredAssociation());
556             }
557
558             if (entity.getType().equals(property.getRawType())) {
559                 return;
560             }
561
562             property.getPersistentEntityTypes().forEach(AbstractMappingContext.this::addPersistentEntity);
563         }
564     }
565
566     /**
567      * Filter rejecting static fields as well as artificially introduced ones. See
568      * {@link PersistentPropertyFilter#UNMAPPED_PROPERTIES} for details.
569      *
570      * @author Oliver Gierke
571      */

572     static enum PersistentPropertyFilter implements FieldFilter {
573
574         INSTANCE;
575
576         private static final Streamable<PropertyMatch> UNMAPPED_PROPERTIES;
577
578         static {
579
580             Set<PropertyMatch> matches = new HashSet<>();
581             matches.add(new PropertyMatch("class"null));
582             matches.add(new PropertyMatch("this\\$.*"null));
583             matches.add(new PropertyMatch("metaClass""groovy.lang.MetaClass"));
584
585             UNMAPPED_PROPERTIES = Streamable.of(matches);
586         }
587
588         /*
589          * (non-Javadoc)
590          * @see org.springframework.util.ReflectionUtils.FieldFilter#matches(java.lang.reflect.Field)
591          */

592         public boolean matches(Field field) {
593
594             if (Modifier.isStatic(field.getModifiers())) {
595                 return false;
596             }
597
598             return !UNMAPPED_PROPERTIES.stream()//
599                     .anyMatch(it -> it.matches(field.getName(), field.getType()));
600         }
601
602         /**
603          * Returns whether the given {@link PropertyDescriptor} is one to create a {@link PersistentProperty} for.
604          *
605          * @param property must not be {@literal null}.
606          * @return
607          */

608         public boolean matches(Property property) {
609
610             Assert.notNull(property, "Property must not be null!");
611
612             if (!property.hasAccessor()) {
613                 return false;
614             }
615
616             return !UNMAPPED_PROPERTIES.stream()//
617                     .anyMatch(it -> it.matches(property.getName(), property.getType()));
618         }
619
620         /**
621          * Value object to help defining property exclusion based on name patterns and types.
622          *
623          * @since 1.4
624          * @author Oliver Gierke
625          */

626         static class PropertyMatch {
627
628             private final @Nullable String namePattern, typeName;
629
630             /**
631              * Creates a new {@link PropertyMatch} for the given name pattern and type name. At least one of the parameters
632              * must not be {@literal null}.
633              *
634              * @param namePattern a regex pattern to match field names, can be {@literal null}.
635              * @param typeName the name of the type to exclude, can be {@literal null}.
636              */

637             public PropertyMatch(@Nullable String namePattern, @Nullable String typeName) {
638
639                 Assert.isTrue(!(namePattern == null && typeName == null), "Either name pattern or type name must be given!");
640
641                 this.namePattern = namePattern;
642                 this.typeName = typeName;
643             }
644
645             /**
646              * Returns whether the given {@link Field} matches the defined {@link PropertyMatch}.
647              *
648              * @param name must not be {@literal null}.
649              * @param type must not be {@literal null}.
650              * @return
651              */

652             public boolean matches(String name, Class<?> type) {
653
654                 Assert.notNull(name, "Name must not be null!");
655                 Assert.notNull(type, "Type must not be null!");
656
657                 if (namePattern != null && !name.matches(namePattern)) {
658                     return false;
659                 }
660
661                 if (typeName != null && !type.getName().equals(typeName)) {
662                     return false;
663                 }
664
665                 return true;
666             }
667         }
668     }
669 }
670