1 /*
2  * Copyright 2012-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.jpa.mapping;
17
18 import java.lang.annotation.Annotation;
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.HashSet;
22 import java.util.Set;
23
24 import javax.persistence.*;
25 import javax.persistence.metamodel.Metamodel;
26
27 import org.springframework.core.annotation.AnnotationUtils;
28 import org.springframework.data.annotation.AccessType.Type;
29 import org.springframework.data.jpa.util.JpaMetamodel;
30 import org.springframework.data.mapping.Association;
31 import org.springframework.data.mapping.PersistentEntity;
32 import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
33 import org.springframework.data.mapping.model.Property;
34 import org.springframework.data.mapping.model.SimpleTypeHolder;
35 import org.springframework.data.util.ClassTypeInformation;
36 import org.springframework.data.util.Lazy;
37 import org.springframework.data.util.TypeInformation;
38 import org.springframework.lang.Nullable;
39 import org.springframework.util.Assert;
40
41 /**
42  * {@link JpaPersistentProperty} implementation usind a JPA {@link Metamodel}.
43  *
44  * @author Oliver Gierke
45  * @author Thomas Darimont
46  * @author Greg Turnquist
47  * @author Christoph Strobl
48  * @author Mark Paluch
49  * @since 1.3
50  */

51 class JpaPersistentPropertyImpl extends AnnotationBasedPersistentProperty<JpaPersistentProperty>
52         implements JpaPersistentProperty {
53
54     private static final Collection<Class<? extends Annotation>> ASSOCIATION_ANNOTATIONS;
55     private static final Collection<Class<? extends Annotation>> ID_ANNOTATIONS;
56     private static final Collection<Class<? extends Annotation>> UPDATEABLE_ANNOTATIONS;
57
58     static {
59
60         Set<Class<? extends Annotation>> annotations = new HashSet<Class<? extends Annotation>>();
61         annotations.add(OneToMany.class);
62         annotations.add(OneToOne.class);
63         annotations.add(ManyToMany.class);
64         annotations.add(ManyToOne.class);
65
66         ASSOCIATION_ANNOTATIONS = Collections.unmodifiableSet(annotations);
67
68         annotations = new HashSet<Class<? extends Annotation>>();
69         annotations.add(Id.class);
70         annotations.add(EmbeddedId.class);
71
72         ID_ANNOTATIONS = Collections.unmodifiableSet(annotations);
73
74         annotations = new HashSet<Class<? extends Annotation>>();
75         annotations.add(Column.class);
76         annotations.add(OrderColumn.class);
77
78         UPDATEABLE_ANNOTATIONS = Collections.unmodifiableSet(annotations);
79     }
80
81     private final @Nullable Boolean usePropertyAccess;
82     private final @Nullable TypeInformation<?> associationTargetType;
83     private final boolean updateable;
84
85     private final Lazy<Boolean> isIdProperty;
86     private final Lazy<Boolean> isAssociation;
87     private final Lazy<Boolean> isEntity;
88
89     /**
90      * Creates a new {@link JpaPersistentPropertyImpl}
91      *
92      * @param metamodel must not be {@literal null}.
93      * @param property must not be {@literal null}.
94      * @param owner must not be {@literal null}.
95      * @param simpleTypeHolder must not be {@literal null}.
96      */

97     public JpaPersistentPropertyImpl(JpaMetamodel metamodel, Property property,
98             PersistentEntity<?, JpaPersistentProperty> owner, SimpleTypeHolder simpleTypeHolder) {
99
100         super(property, owner, simpleTypeHolder);
101
102         Assert.notNull(metamodel, "Metamodel must not be null!");
103
104         this.isAssociation = Lazy.of(() -> ASSOCIATION_ANNOTATIONS.stream().anyMatch(this::isAnnotationPresent));
105         this.usePropertyAccess = detectPropertyAccess();
106         this.associationTargetType = detectAssociationTargetType();
107         this.updateable = detectUpdatability();
108
109         this.isIdProperty = Lazy.of(() -> ID_ANNOTATIONS.stream().anyMatch(it -> isAnnotationPresent(it)) //
110                 || metamodel.isSingleIdAttribute(getOwner().getType(), getName(), getType()));
111         this.isEntity = Lazy.of(() -> metamodel.isJpaManaged(getActualType()));
112     }
113
114     /*
115      * (non-Javadoc)
116      * @see org.springframework.data.mapping.model.AbstractPersistentProperty#getActualType()
117      */

118     @Override
119     public Class<?> getActualType() {
120         return associationTargetType != null ? associationTargetType.getType() : super.getActualType();
121     }
122
123     /* 
124      * (non-Javadoc)
125      * @see org.springframework.data.mapping.PersistentProperty#getPersistentEntityTypes()
126      */

127     @Override
128     public Iterable<? extends TypeInformation<?>> getPersistentEntityTypes() {
129
130         return associationTargetType != null //
131                 ? Collections.singleton(associationTargetType) //
132                 : super.getPersistentEntityTypes();
133     }
134
135     /*
136      * (non-Javadoc)
137      * @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#isIdProperty()
138      */

139     @Override
140     public boolean isIdProperty() {
141         return isIdProperty.get();
142     }
143
144     /*
145      * (non-Javadoc)
146      * @see org.springframework.data.mapping.model.AbstractPersistentProperty#isEntity()
147      */

148     @Override
149     public boolean isEntity() {
150         return isEntity.get();
151     }
152
153     /*
154      * (non-Javadoc)
155      * @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#isAssociation()
156      */

157     @Override
158     public boolean isAssociation() {
159         return isAssociation.get();
160     }
161
162     /*
163      * (non-Javadoc)
164      * @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#isTransient()
165      */

166     @Override
167     public boolean isTransient() {
168         return isAnnotationPresent(Transient.class) || super.isTransient();
169     }
170
171     /*
172      * (non-Javadoc)
173      * @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation()
174      */

175     @Override
176     protected Association<JpaPersistentProperty> createAssociation() {
177         return new Association<JpaPersistentProperty>(thisnull);
178     }
179
180     /*
181      * (non-Javadoc)
182      * @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#usePropertyAccess()
183      */

184     @Override
185     public boolean usePropertyAccess() {
186         return usePropertyAccess != null ? usePropertyAccess : super.usePropertyAccess();
187     }
188
189     /*
190      * (non-Javadoc)
191      * @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#isVersionProperty()
192      */

193     @Override
194     public boolean isVersionProperty() {
195         return isAnnotationPresent(Version.class);
196     }
197
198     /*
199      * (non-Javadoc)
200      * @see org.springframework.data.mapping.model.AnnotationBasedPersistentProperty#isWritable()
201      */

202     @Override
203     public boolean isWritable() {
204         return updateable && super.isWritable();
205     }
206
207     /* 
208      * (non-Javadoc)
209      * @see org.springframework.data.jpa.mapping.JpaPersistentProperty#isEmbeddable()
210      */

211     @Override
212     public boolean isEmbeddable() {
213         return isAnnotationPresent(Embedded.class) || hasActualTypeAnnotation(Embeddable.class);
214     }
215
216     /**
217      * Looks up both Spring Data's and JPA's access type definition annotations on the property or type level to determine
218      * the access type to be used. Will consider property-level annotations over type-level ones, favoring the Spring Data
219      * ones over the JPA ones if found on the same level. Returns {@literal nullif no explicit annotation can be found
220      * falling back to the defaults implemented in the super class.
221      *
222      * @return
223      */

224     @Nullable
225     private Boolean detectPropertyAccess() {
226
227         org.springframework.data.annotation.AccessType accessType = findAnnotation(
228                 org.springframework.data.annotation.AccessType.class);
229
230         if (accessType != null) {
231             return Type.PROPERTY.equals(accessType.value());
232         }
233
234         Access access = findAnnotation(Access.class);
235
236         if (access != null) {
237             return AccessType.PROPERTY.equals(access.value());
238         }
239
240         accessType = findPropertyOrOwnerAnnotation(org.springframework.data.annotation.AccessType.class);
241
242         if (accessType != null) {
243             return Type.PROPERTY.equals(accessType.value());
244         }
245
246         access = findPropertyOrOwnerAnnotation(Access.class);
247
248         if (access != null) {
249             return AccessType.PROPERTY.equals(access.value());
250         }
251
252         return null;
253     }
254
255     /**
256      * Inspects the association annotations on the property and returns the target entity type if specified.
257      *
258      * @return
259      */

260     @Nullable
261     private TypeInformation<?> detectAssociationTargetType() {
262
263         if (!isAssociation()) {
264             return null;
265         }
266
267         for (Class<? extends Annotation> annotationType : ASSOCIATION_ANNOTATIONS) {
268
269             Annotation annotation = findAnnotation(annotationType);
270
271             if (annotation == null) {
272                 continue;
273             }
274
275             Object entityValue = AnnotationUtils.getValue(annotation, "targetEntity");
276
277             if (entityValue == null || entityValue.equals(void.class)) {
278                 continue;
279             }
280
281             return ClassTypeInformation.from((Class<?>) entityValue);
282         }
283
284         return null;
285     }
286
287     /**
288      * Checks whether {@code updatable} attribute of any of the {@link #UPDATEABLE_ANNOTATIONS} is configured to
289      * {@literal true}.
290      *
291      * @return
292      */

293     private boolean detectUpdatability() {
294
295         for (Class<? extends Annotation> annotationType : UPDATEABLE_ANNOTATIONS) {
296
297             Annotation annotation = findAnnotation(annotationType);
298
299             if (annotation == null) {
300                 continue;
301             }
302
303             return (boolean) AnnotationUtils.getValue(annotation, "updatable");
304         }
305
306         return true;
307     }
308 }
309