1 /*
2 * Copyright 2016-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.util;
17
18 import java.util.Collection;
19 import java.util.Map;
20 import java.util.Optional;
21 import java.util.concurrent.ConcurrentHashMap;
22
23 import javax.persistence.metamodel.EntityType;
24 import javax.persistence.metamodel.ManagedType;
25 import javax.persistence.metamodel.Metamodel;
26 import javax.persistence.metamodel.SingularAttribute;
27
28 import org.springframework.data.util.Lazy;
29 import org.springframework.data.util.StreamUtils;
30 import org.springframework.util.Assert;
31
32 /**
33 * Wrapper around the JPA {@link Metamodel} to be able to apply some fixes against bugs in provider implementations.
34 *
35 * @author Oliver Gierke
36 * @author Mark Paluch
37 * @author Sylvère Richard
38 */
39 public class JpaMetamodel {
40
41 private static final Map<Metamodel, JpaMetamodel> CACHE = new ConcurrentHashMap<>(4);
42
43 private final Metamodel metamodel;
44
45 private Lazy<Collection<Class<?>>> managedTypes;
46
47 /**
48 * Creates a new {@link JpaMetamodel} for the given JPA {@link Metamodel}.
49 *
50 * @param metamodel must not be {@literal null}.
51 */
52 private JpaMetamodel(Metamodel metamodel) {
53
54 Assert.notNull(metamodel, "Metamodel must not be null!");
55
56 this.metamodel = metamodel;
57 this.managedTypes = Lazy.of(() -> metamodel.getManagedTypes().stream() //
58 .map(ManagedType::getJavaType) //
59 .filter(it -> it != null) //
60 .collect(StreamUtils.toUnmodifiableSet()));
61 }
62
63 public static JpaMetamodel of(Metamodel metamodel) {
64 return CACHE.computeIfAbsent(metamodel, JpaMetamodel::new);
65 }
66
67 /**
68 * Returns whether the given type is managed by the backing JPA {@link Metamodel}.
69 *
70 * @param type must not be {@literal null}.
71 * @return
72 */
73 public boolean isJpaManaged(Class<?> type) {
74
75 Assert.notNull(type, "Type must not be null!");
76
77 return managedTypes.get().contains(type);
78 }
79
80 /**
81 * Returns whether the attribute of given name and type is the single identifier attribute of the given entity.
82 *
83 * @param entity must not be {@literal null}.
84 * @param name must not be {@literal null}.
85 * @param attributeType must not be {@literal null}.
86 * @return
87 */
88 public boolean isSingleIdAttribute(Class<?> entity, String name, Class<?> attributeType) {
89
90 return metamodel.getEntities().stream() //
91 .filter(it -> entity.equals(it.getJavaType())) //
92 .findFirst() //
93 .flatMap(it -> getSingularIdAttribute(it)) //
94 .filter(it -> it.getJavaType().equals(attributeType)) //
95 .map(it -> it.getName().equals(name)) //
96 .orElse(false);
97 }
98
99 /**
100 * Wipes the static cache of {@link Metamodel} to {@link JpaMetamodel}.
101 */
102 static void clear() {
103 CACHE.clear();
104 }
105
106 /**
107 * Returns the {@link SingularAttribute} representing the identifier of the given {@link EntityType} if it contains a
108 * singular one.
109 *
110 * @param entityType must not be {@literal null}.
111 * @return
112 */
113 private static Optional<? extends SingularAttribute<?, ?>> getSingularIdAttribute(EntityType<?> entityType) {
114
115 if (!entityType.hasSingleIdAttribute()) {
116 return Optional.empty();
117 }
118
119 return entityType.getSingularAttributes().stream() //
120 .filter(SingularAttribute::isId) //
121 .findFirst();
122 }
123 }
124