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.repository.core.support;
17
18 import static org.springframework.data.repository.util.ClassUtils.*;
19 import static org.springframework.util.ReflectionUtils.*;
20
21 import java.lang.reflect.Method;
22 import java.lang.reflect.Modifier;
23 import java.util.Collections;
24 import java.util.HashSet;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.concurrent.ConcurrentHashMap;
28
29 import org.springframework.core.annotation.AnnotationUtils;
30 import org.springframework.data.annotation.QueryAnnotation;
31 import org.springframework.data.repository.core.CrudMethods;
32 import org.springframework.data.repository.core.RepositoryInformation;
33 import org.springframework.data.repository.core.RepositoryMetadata;
34 import org.springframework.data.util.Streamable;
35 import org.springframework.data.util.TypeInformation;
36 import org.springframework.util.Assert;
37 import org.springframework.util.ClassUtils;
38
39 /**
40  * Default implementation of {@link RepositoryInformation}.
41  *
42  * @author Oliver Gierke
43  * @author Thomas Darimont
44  * @author Mark Paluch
45  * @author Christoph Strobl
46  */

47 class DefaultRepositoryInformation implements RepositoryInformation {
48
49     private final Map<Method, Method> methodCache = new ConcurrentHashMap<>();
50
51     private final RepositoryMetadata metadata;
52     private final Class<?> repositoryBaseClass;
53     private final RepositoryComposition composition;
54     private final RepositoryComposition baseComposition;
55
56     /**
57      * Creates a new {@link DefaultRepositoryMetadata} for the given repository interface and repository base class.
58      *
59      * @param metadata must not be {@literal null}.
60      * @param repositoryBaseClass must not be {@literal null}.
61      * @param composition must not be {@literal null}.
62      */

63     public DefaultRepositoryInformation(RepositoryMetadata metadata, Class<?> repositoryBaseClass,
64             RepositoryComposition composition) {
65
66         Assert.notNull(metadata, "Repository metadata must not be null!");
67         Assert.notNull(repositoryBaseClass, "Repository base class must not be null!");
68         Assert.notNull(composition, "Repository composition must not be null!");
69
70         this.metadata = metadata;
71         this.repositoryBaseClass = repositoryBaseClass;
72         this.composition = composition;
73         this.baseComposition = RepositoryComposition.of(RepositoryFragment.structural(repositoryBaseClass)) //
74                 .withArgumentConverter(composition.getArgumentConverter()) //
75                 .withMethodLookup(composition.getMethodLookup());
76     }
77
78     /*
79      * (non-Javadoc)
80      * @see org.springframework.data.repository.support.RepositoryMetadata#getDomainClass()
81      */

82     @Override
83     public Class<?> getDomainType() {
84         return metadata.getDomainType();
85     }
86
87     /*
88      * (non-Javadoc)
89      * @see org.springframework.data.repository.support.RepositoryMetadata#getIdClass()
90      */

91     @Override
92     public Class<?> getIdType() {
93         return metadata.getIdType();
94     }
95
96     /*
97      * (non-Javadoc)
98      * @see org.springframework.data.repository.support.RepositoryInformation#getRepositoryBaseClass()
99      */

100     @Override
101     public Class<?> getRepositoryBaseClass() {
102         return this.repositoryBaseClass;
103     }
104
105     /*
106      * (non-Javadoc)
107      * @see org.springframework.data.repository.support.RepositoryInformation#getTargetClassMethod(java.lang.reflect.Method)
108      */

109     @Override
110     public Method getTargetClassMethod(Method method) {
111
112         if (methodCache.containsKey(method)) {
113             return methodCache.get(method);
114         }
115
116         Method result = composition.findMethod(method).orElse(method);
117
118         if (!result.equals(method)) {
119             return cacheAndReturn(method, result);
120         }
121
122         return cacheAndReturn(method, baseComposition.findMethod(method).orElse(method));
123     }
124
125     private Method cacheAndReturn(Method key, Method value) {
126
127         if (value != null) {
128             makeAccessible(value);
129         }
130
131         methodCache.put(key, value);
132         return value;
133     }
134
135     /*
136      * (non-Javadoc)
137      * @see org.springframework.data.repository.support.RepositoryInformation#getQueryMethods()
138      */

139     @Override
140     public Streamable<Method> getQueryMethods() {
141
142         Set<Method> result = new HashSet<>();
143
144         for (Method method : getRepositoryInterface().getMethods()) {
145             method = ClassUtils.getMostSpecificMethod(method, getRepositoryInterface());
146             if (isQueryMethodCandidate(method)) {
147                 result.add(method);
148             }
149         }
150
151         return Streamable.of(Collections.unmodifiableSet(result));
152     }
153
154     /**
155      * Checks whether the given method is a query method candidate.
156      *
157      * @param method
158      * @return
159      */

160     private boolean isQueryMethodCandidate(Method method) {
161         return !method.isBridge() && !method.isDefault() //
162                 && !Modifier.isStatic(method.getModifiers()) //
163                 && (isQueryAnnotationPresentOn(method) || !isCustomMethod(method) && !isBaseClassMethod(method));
164     }
165
166     /**
167      * Checks whether the given method contains a custom store specific query annotation annotated with
168      * {@link QueryAnnotation}. The method-hierarchy is also considered in the search for the annotation.
169      *
170      * @param method
171      * @return
172      */

173     private boolean isQueryAnnotationPresentOn(Method method) {
174
175         return AnnotationUtils.findAnnotation(method, QueryAnnotation.class) != null;
176     }
177
178     /*
179      * (non-Javadoc)
180      * @see org.springframework.data.repository.support.RepositoryInformation#isCustomMethod(java.lang.reflect.Method)
181      */

182     @Override
183     public boolean isCustomMethod(Method method) {
184         return composition.getMethod(method) != null;
185     }
186
187     /*
188      * (non-Javadoc)
189      * @see org.springframework.data.repository.core.RepositoryInformation#isQueryMethod(java.lang.reflect.Method)
190      */

191     @Override
192     public boolean isQueryMethod(Method method) {
193         return getQueryMethods().stream().anyMatch(it -> it.equals(method));
194     }
195
196     /*
197      * (non-Javadoc)
198      * @see org.springframework.data.repository.core.RepositoryInformation#isBaseClassMethod(java.lang.reflect.Method)
199      */

200     @Override
201     public boolean isBaseClassMethod(Method method) {
202
203         Assert.notNull(method, "Method must not be null!");
204         return baseComposition.getMethod(method) != null;
205     }
206
207     /*
208      * (non-Javadoc)
209      * @see org.springframework.data.repository.support.RepositoryInformation#hasCustomMethod()
210      */

211     @Override
212     public boolean hasCustomMethod() {
213
214         Class<?> repositoryInterface = getRepositoryInterface();
215
216         // No detection required if no typing interface was configured
217         if (isGenericRepositoryInterface(repositoryInterface)) {
218             return false;
219         }
220
221         for (Method method : repositoryInterface.getMethods()) {
222             if (isCustomMethod(method) && !isBaseClassMethod(method)) {
223                 return true;
224             }
225         }
226
227         return false;
228     }
229
230     /*
231      * (non-Javadoc)
232      * @see org.springframework.data.repository.core.RepositoryMetadata#getRepositoryInterface()
233      */

234     @Override
235     public Class<?> getRepositoryInterface() {
236         return metadata.getRepositoryInterface();
237     }
238
239     /*
240      * (non-Javadoc)
241      * @see org.springframework.data.repository.core.RepositoryMetadata#getReturnedDomainClass(java.lang.reflect.Method)
242      */

243     @Override
244     public Class<?> getReturnedDomainClass(Method method) {
245         return metadata.getReturnedDomainClass(method);
246     }
247
248     /*
249      * (non-Javadoc)
250      * @see org.springframework.data.repository.core.RepositoryMetadata#getReturnType(java.lang.reflect.Method)
251      */

252     @Override
253     public TypeInformation<?> getReturnType(Method method) {
254         return metadata.getReturnType(method);
255     }
256
257     /*
258      * (non-Javadoc)
259      * @see org.springframework.data.repository.core.RepositoryMetadata#getCrudMethods()
260      */

261     @Override
262     public CrudMethods getCrudMethods() {
263         return metadata.getCrudMethods();
264     }
265
266     /*
267      * (non-Javadoc)
268      * @see org.springframework.data.repository.core.RepositoryMetadata#isPagingRepository()
269      */

270     @Override
271     public boolean isPagingRepository() {
272         return metadata.isPagingRepository();
273     }
274
275     /*
276      * (non-Javadoc)
277      * @see org.springframework.data.repository.core.RepositoryMetadata#getAlternativeDomainTypes()
278      */

279     @Override
280     public Set<Class<?>> getAlternativeDomainTypes() {
281         return metadata.getAlternativeDomainTypes();
282     }
283
284     /*
285      * (non-Javadoc)
286      * @see org.springframework.data.repository.core.RepositoryMetadata#isReactiveRepository()
287      */

288     @Override
289     public boolean isReactiveRepository() {
290         return metadata.isReactiveRepository();
291     }
292 }
293