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