1
16 package org.springframework.data.repository.query;
17
18 import static org.springframework.data.repository.util.ClassUtils.*;
19
20 import java.lang.reflect.Method;
21 import java.util.Set;
22 import java.util.stream.Stream;
23
24 import org.springframework.data.domain.Page;
25 import org.springframework.data.domain.Pageable;
26 import org.springframework.data.domain.Slice;
27 import org.springframework.data.domain.Sort;
28 import org.springframework.data.projection.ProjectionFactory;
29 import org.springframework.data.repository.core.EntityMetadata;
30 import org.springframework.data.repository.core.RepositoryMetadata;
31 import org.springframework.data.repository.util.QueryExecutionConverters;
32 import org.springframework.data.util.ClassTypeInformation;
33 import org.springframework.data.util.Lazy;
34 import org.springframework.data.util.TypeInformation;
35 import org.springframework.util.Assert;
36
37
47 public class QueryMethod {
48
49 private final RepositoryMetadata metadata;
50 private final Method method;
51 private final Class<?> unwrappedReturnType;
52 private final Parameters<?, ?> parameters;
53 private final ResultProcessor resultProcessor;
54 private final Lazy<Class<?>> domainClass;
55 private final Lazy<Boolean> isCollectionQuery;
56
57
65 public QueryMethod(Method method, RepositoryMetadata metadata, ProjectionFactory factory) {
66
67 Assert.notNull(method, "Method must not be null!");
68 Assert.notNull(metadata, "Repository metadata must not be null!");
69 Assert.notNull(factory, "ProjectionFactory must not be null!");
70
71 Parameters.TYPES.stream()
72 .filter(type -> getNumberOfOccurences(method, type) > 1)
73 .findFirst().ifPresent(type -> {
74 throw new IllegalStateException(
75 String.format("Method must only one argument of type %s! Offending method: %s", type.getSimpleName(),
76 method.toString()));
77 });
78
79 this.method = method;
80 this.unwrappedReturnType = potentiallyUnwrapReturnTypeFor(method);
81 this.parameters = createParameters(method);
82 this.metadata = metadata;
83
84 if (hasParameterOfType(method, Pageable.class)) {
85
86 if (!isStreamQuery()) {
87 assertReturnTypeAssignable(method, QueryExecutionConverters.getAllowedPageableTypes());
88 }
89
90 if (hasParameterOfType(method, Sort.class)) {
91 throw new IllegalStateException(String.format("Method must not have Pageable *and* Sort parameter. "
92 + "Use sorting capabilities on Pageable instead! Offending method: %s", method.toString()));
93 }
94 }
95
96 Assert.notNull(this.parameters,
97 () -> String.format("Parameters extracted from method '%s' must not be null!", method.getName()));
98
99 if (isPageQuery()) {
100 Assert.isTrue(this.parameters.hasPageableParameter(),
101 String.format("Paging query needs to have a Pageable parameter! Offending method %s", method.toString()));
102 }
103
104 this.domainClass = Lazy.of(() -> {
105
106 Class<?> repositoryDomainClass = metadata.getDomainType();
107 Class<?> methodDomainClass = metadata.getReturnedDomainClass(method);
108
109 return repositoryDomainClass == null || repositoryDomainClass.isAssignableFrom(methodDomainClass)
110 ? methodDomainClass
111 : repositoryDomainClass;
112 });
113
114 this.resultProcessor = new ResultProcessor(this, factory);
115 this.isCollectionQuery = Lazy.of(this::calculateIsCollectionQuery);
116 }
117
118
124 protected Parameters<?, ?> createParameters(Method method) {
125 return new DefaultParameters(method);
126 }
127
128
133 public String getName() {
134 return method.getName();
135 }
136
137 @SuppressWarnings({ "rawtypes", "unchecked" })
138 public EntityMetadata<?> getEntityInformation() {
139 return () -> (Class) getDomainClass();
140 }
141
142
147 public String getNamedQueryName() {
148 return String.format("%s.%s", getDomainClass().getSimpleName(), method.getName());
149 }
150
151
156 protected Class<?> getDomainClass() {
157 return domainClass.get();
158 }
159
160
165 public Class<?> getReturnedObjectType() {
166 return metadata.getReturnedDomainClass(method);
167 }
168
169
174 public boolean isCollectionQuery() {
175 return isCollectionQuery.get();
176 }
177
178
184 public boolean isSliceQuery() {
185 return !isPageQuery() && org.springframework.util.ClassUtils.isAssignable(Slice.class, unwrappedReturnType);
186 }
187
188
193 public final boolean isPageQuery() {
194 return org.springframework.util.ClassUtils.isAssignable(Page.class, unwrappedReturnType);
195 }
196
197
202 public boolean isModifyingQuery() {
203 return false;
204 }
205
206
211 public boolean isQueryForEntity() {
212 return getDomainClass().isAssignableFrom(getReturnedObjectType());
213 }
214
215
221 public boolean isStreamQuery() {
222 return Stream.class.isAssignableFrom(unwrappedReturnType);
223 }
224
225
230 public Parameters<?, ?> getParameters() {
231 return parameters;
232 }
233
234
239 public ResultProcessor getResultProcessor() {
240 return resultProcessor;
241 }
242
243
247 @Override
248 public String toString() {
249 return method.toString();
250 }
251
252 private boolean calculateIsCollectionQuery() {
253
254 if (isPageQuery() || isSliceQuery()) {
255 return false;
256 }
257
258 Class<?> returnType = method.getReturnType();
259
260 if (QueryExecutionConverters.supports(returnType) && !QueryExecutionConverters.isSingleValue(returnType)) {
261 return true;
262 }
263
264 if (QueryExecutionConverters.supports(unwrappedReturnType)) {
265 return !QueryExecutionConverters.isSingleValue(unwrappedReturnType);
266 }
267
268 return ClassTypeInformation.from(unwrappedReturnType).isCollectionLike();
269 }
270
271 private static Class<? extends Object> potentiallyUnwrapReturnTypeFor(Method method) {
272
273 if (QueryExecutionConverters.supports(method.getReturnType())) {
274
275
276
277 TypeInformation<?> componentType = ClassTypeInformation.fromReturnTypeOf(method).getComponentType();
278
279 if (componentType == null) {
280 throw new IllegalStateException(
281 String.format("Couldn't find component type for return value of method %s!", method));
282 }
283
284 return componentType.getType();
285 }
286
287 return method.getReturnType();
288 }
289
290 private static void assertReturnTypeAssignable(Method method, Set<Class<?>> types) {
291
292 Assert.notNull(method, "Method must not be null!");
293 Assert.notEmpty(types, "Types must not be null or empty!");
294
295 TypeInformation<?> returnType = ClassTypeInformation.fromReturnTypeOf(method);
296
297 returnType = QueryExecutionConverters.isSingleValue(returnType.getType())
298 ? returnType.getRequiredComponentType()
299 : returnType;
300
301 for (Class<?> type : types) {
302 if (type.isAssignableFrom(returnType.getType())) {
303 return;
304 }
305 }
306
307 throw new IllegalStateException("Method has to have one of the following return types! " + types.toString());
308 }
309 }
310