1
16 package org.springframework.data.jpa.repository.support;
17
18 import java.util.ArrayList;
19 import java.util.Collections;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Optional;
23 import java.util.Set;
24
25 import javax.persistence.IdClass;
26 import javax.persistence.metamodel.Attribute;
27 import javax.persistence.metamodel.EntityType;
28 import javax.persistence.metamodel.IdentifiableType;
29 import javax.persistence.metamodel.ManagedType;
30 import javax.persistence.metamodel.Metamodel;
31 import javax.persistence.metamodel.SingularAttribute;
32 import javax.persistence.metamodel.Type;
33 import javax.persistence.metamodel.Type.PersistenceType;
34
35 import org.springframework.beans.BeanWrapper;
36 import org.springframework.beans.BeanWrapperImpl;
37 import org.springframework.core.annotation.AnnotationUtils;
38 import org.springframework.data.jpa.provider.PersistenceProvider;
39 import org.springframework.data.jpa.util.JpaMetamodel;
40 import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
41 import org.springframework.data.util.ProxyUtils;
42 import org.springframework.lang.Nullable;
43 import org.springframework.util.Assert;
44
45
55 public class JpaMetamodelEntityInformation<T, ID> extends JpaEntityInformationSupport<T, ID> {
56
57 private final IdMetadata<T> idMetadata;
58 private final Optional<SingularAttribute<? super T, ?>> versionAttribute;
59 private final Metamodel metamodel;
60 private final @Nullable String entityName;
61
62
68 public JpaMetamodelEntityInformation(Class<T> domainClass, Metamodel metamodel) {
69
70 super(domainClass);
71
72 Assert.notNull(metamodel, "Metamodel must not be null!");
73 this.metamodel = metamodel;
74
75 ManagedType<T> type = metamodel.managedType(domainClass);
76
77 if (type == null) {
78 throw new IllegalArgumentException("The given domain class can not be found in the given Metamodel!");
79 }
80
81 this.entityName = type instanceof EntityType ? ((EntityType<?>) type).getName() : null;
82
83 if (!(type instanceof IdentifiableType)) {
84 throw new IllegalArgumentException("The given domain class does not contain an id attribute!");
85 }
86
87 IdentifiableType<T> identifiableType = (IdentifiableType<T>) type;
88
89 this.idMetadata = new IdMetadata<>(identifiableType);
90 this.versionAttribute = findVersionAttribute(identifiableType, metamodel);
91 }
92
93
97 @Override
98 public String getEntityName() {
99 return entityName != null ? entityName : super.getEntityName();
100 }
101
102
109 @SuppressWarnings("unchecked")
110 private static <T> Optional<SingularAttribute<? super T, ?>> findVersionAttribute(IdentifiableType<T> type,
111 Metamodel metamodel) {
112
113 try {
114 return Optional.ofNullable(type.getVersion(Object.class));
115 } catch (IllegalArgumentException o_O) {
116
117 }
118
119 Set<SingularAttribute<? super T, ?>> attributes = type.getSingularAttributes();
120
121 for (SingularAttribute<? super T, ?> attribute : attributes) {
122 if (attribute.isVersion()) {
123 return Optional.of(attribute);
124 }
125 }
126
127 Class<?> superType = type.getJavaType().getSuperclass();
128
129 if (!JpaMetamodel.of(metamodel).isJpaManaged(superType)) {
130 return Optional.empty();
131 }
132
133 ManagedType<?> managedSuperType = metamodel.managedType(superType);
134
135 if (!(managedSuperType instanceof IdentifiableType)) {
136 return Optional.empty();
137 }
138
139 return findVersionAttribute((IdentifiableType<T>) managedSuperType, metamodel);
140 }
141
142
146 @Override
147 @Nullable
148 @SuppressWarnings("unchecked")
149 public ID getId(T entity) {
150
151
152 PersistenceProvider persistenceProvider = PersistenceProvider.fromMetamodel(metamodel);
153
154 if (persistenceProvider.shouldUseAccessorFor(entity)) {
155 return (ID) persistenceProvider.getIdentifierFrom(entity);
156 }
157
158
159 BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
160
161 if (idMetadata.hasSimpleId()) {
162 return (ID) entityWrapper.getPropertyValue(idMetadata.getSimpleIdAttribute().getName());
163 }
164
165 BeanWrapper idWrapper = new IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(idMetadata.getType(), metamodel);
166 boolean partialIdValueFound = false;
167
168 for (SingularAttribute<? super T, ?> attribute : idMetadata) {
169 Object propertyValue = entityWrapper.getPropertyValue(attribute.getName());
170
171 if (propertyValue != null) {
172 partialIdValueFound = true;
173 }
174
175 idWrapper.setPropertyValue(attribute.getName(), propertyValue);
176 }
177
178 return partialIdValueFound ? (ID) idWrapper.getWrappedInstance() : null;
179 }
180
181
185 @Override
186 @SuppressWarnings("unchecked")
187 public Class<ID> getIdType() {
188 return (Class<ID>) idMetadata.getType();
189 }
190
191
195 @Override
196 public SingularAttribute<? super T, ?> getIdAttribute() {
197 return idMetadata.getSimpleIdAttribute();
198 }
199
200
204 @Override
205 public boolean hasCompositeId() {
206 return !idMetadata.hasSimpleId();
207 }
208
209
213 @Override
214 public Iterable<String> getIdAttributeNames() {
215
216 List<String> attributeNames = new ArrayList<>(idMetadata.attributes.size());
217
218 for (SingularAttribute<? super T, ?> attribute : idMetadata.attributes) {
219 attributeNames.add(attribute.getName());
220 }
221
222 return attributeNames;
223 }
224
225
229 @Override
230 public Object getCompositeIdAttributeValue(Object id, String idAttribute) {
231
232 Assert.isTrue(hasCompositeId(), "Model must have a composite Id!");
233
234 return new DirectFieldAccessFallbackBeanWrapper(id).getPropertyValue(idAttribute);
235 }
236
237
241 @Override
242 public boolean isNew(T entity) {
243
244 if (!versionAttribute.isPresent()
245 || versionAttribute.map(Attribute::getJavaType).map(Class::isPrimitive).orElse(false)) {
246 return super.isNew(entity);
247 }
248
249 BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
250
251 return versionAttribute.map(it -> wrapper.getPropertyValue(it.getName()) == null).orElse(true);
252 }
253
254
260 private static class IdMetadata<T> implements Iterable<SingularAttribute<? super T, ?>> {
261
262 private final IdentifiableType<T> type;
263 private final Set<SingularAttribute<? super T, ?>> attributes;
264 private @Nullable Class<?> idType;
265
266 @SuppressWarnings("unchecked")
267 IdMetadata(IdentifiableType<T> source) {
268
269 this.type = source;
270 this.attributes = (Set<SingularAttribute<? super T, ?>>) (source.hasSingleIdAttribute()
271 ? Collections.singleton(source.getId(source.getIdType().getJavaType()))
272 : source.getIdClassAttributes());
273 }
274
275 boolean hasSimpleId() {
276 return attributes.size() == 1;
277 }
278
279 public Class<?> getType() {
280
281 if (idType != null) {
282 return idType;
283 }
284
285
286 this.idType = tryExtractIdTypeWithFallbackToIdTypeLookup();
287
288 if (this.idType == null) {
289 throw new IllegalStateException("Cannot resolve Id type from " + type);
290 }
291
292 return this.idType;
293 }
294
295 @Nullable
296 private Class<?> tryExtractIdTypeWithFallbackToIdTypeLookup() {
297
298 try {
299 Type<?> idType = type.getIdType();
300 return idType == null ? fallbackIdTypeLookup(type) : idType.getJavaType();
301 } catch (IllegalStateException e) {
302
303 return fallbackIdTypeLookup(type);
304 }
305 }
306
307 @Nullable
308 private static Class<?> fallbackIdTypeLookup(IdentifiableType<?> type) {
309
310 IdClass annotation = AnnotationUtils.findAnnotation(type.getJavaType(), IdClass.class);
311 return annotation == null ? null : annotation.value();
312 }
313
314 SingularAttribute<? super T, ?> getSimpleIdAttribute() {
315 return attributes.iterator().next();
316 }
317
318
322 @Override
323 public Iterator<SingularAttribute<? super T, ?>> iterator() {
324 return attributes.iterator();
325 }
326 }
327
328
334 private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper
335 extends DirectFieldAccessFallbackBeanWrapper {
336
337 private final Metamodel metamodel;
338 private final JpaMetamodel jpaMetamodel;
339
340 IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(Class<?> type, Metamodel metamodel) {
341 super(type);
342 this.metamodel = metamodel;
343 this.jpaMetamodel = JpaMetamodel.of(metamodel);
344 }
345
346
350 @Override
351 @SuppressWarnings({ "unchecked", "rawtypes" })
352 public void setPropertyValue(String propertyName, @Nullable Object value) {
353
354 if (!isIdentifierDerivationNecessary(value)) {
355 super.setPropertyValue(propertyName, value);
356 return;
357 }
358
359
360 JpaMetamodelEntityInformation nestedEntityInformation = new JpaMetamodelEntityInformation(
361 ProxyUtils.getUserClass(value), this.metamodel);
362
363 if (!nestedEntityInformation.getJavaType().isAnnotationPresent(IdClass.class)) {
364
365 Object nestedIdPropertyValue = new DirectFieldAccessFallbackBeanWrapper(value)
366 .getPropertyValue(nestedEntityInformation.getRequiredIdAttribute().getName());
367 super.setPropertyValue(propertyName, nestedIdPropertyValue);
368 return;
369 }
370
371
372
373
374 BeanWrapper sourceIdValueWrapper = new DirectFieldAccessFallbackBeanWrapper(value);
375 BeanWrapper targetIdClassTypeWrapper = new BeanWrapperImpl(nestedEntityInformation.getIdType());
376
377 for (String idAttributeName : (Iterable<String>) nestedEntityInformation.getIdAttributeNames()) {
378 targetIdClassTypeWrapper.setPropertyValue(idAttributeName,
379 extractActualIdPropertyValue(sourceIdValueWrapper, idAttributeName));
380 }
381
382 super.setPropertyValue(propertyName, targetIdClassTypeWrapper.getWrappedInstance());
383 }
384
385 @Nullable
386 private Object extractActualIdPropertyValue(BeanWrapper sourceIdValueWrapper, String idAttributeName) {
387
388 Object idPropertyValue = sourceIdValueWrapper.getPropertyValue(idAttributeName);
389
390 if (idPropertyValue != null) {
391
392 Class<?> idPropertyValueType = idPropertyValue.getClass();
393
394 if (!jpaMetamodel.isJpaManaged(idPropertyValueType)) {
395 return idPropertyValue;
396 }
397
398 return new DirectFieldAccessFallbackBeanWrapper(idPropertyValue)
399 .getPropertyValue(tryFindSingularIdAttributeNameOrUseFallback(idPropertyValueType, idAttributeName));
400 }
401
402 return null;
403 }
404
405 private String tryFindSingularIdAttributeNameOrUseFallback(Class<?> idPropertyValueType,
406 String fallbackIdTypePropertyName) {
407
408 ManagedType<?> idPropertyType = metamodel.managedType(idPropertyValueType);
409 for (SingularAttribute<?, ?> sa : idPropertyType.getSingularAttributes()) {
410 if (sa.isId()) {
411 return sa.getName();
412 }
413 }
414
415 return fallbackIdTypePropertyName;
416 }
417
418
423 private boolean isIdentifierDerivationNecessary(@Nullable Object value) {
424
425 if (value == null) {
426 return false;
427 }
428
429 Class<?> userClass = ProxyUtils.getUserClass(value);
430
431 if (!this.jpaMetamodel.isJpaManaged(userClass)) {
432 return false;
433 }
434
435 ManagedType<?> managedType = this.metamodel.managedType(userClass);
436
437 if (managedType == null) {
438 throw new IllegalStateException("ManagedType must not be null. We checked that it exists before.");
439 }
440
441 return managedType.getPersistenceType() == PersistenceType.ENTITY;
442 }
443 }
444 }
445