1 /*
2  * Copyright 2008-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.repository.query;
17
18 import java.lang.reflect.Method;
19
20 import javax.persistence.EntityManager;
21
22 import org.springframework.data.jpa.repository.Query;
23 import org.springframework.data.projection.ProjectionFactory;
24 import org.springframework.data.repository.core.NamedQueries;
25 import org.springframework.data.repository.core.RepositoryMetadata;
26 import org.springframework.data.repository.query.QueryLookupStrategy;
27 import org.springframework.data.repository.query.QueryLookupStrategy.Key;
28 import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
29 import org.springframework.data.repository.query.RepositoryQuery;
30 import org.springframework.lang.Nullable;
31 import org.springframework.util.Assert;
32
33 /**
34  * Query lookup strategy to execute finders.
35  *
36  * @author Oliver Gierke
37  * @author Thomas Darimont
38  * @author Mark Paluch
39  * @author RĂ©da Housni Alaoui
40  */

41 public final class JpaQueryLookupStrategy {
42
43     /**
44      * Private constructor to prevent instantiation.
45      */

46     private JpaQueryLookupStrategy() {}
47
48     /**
49      * Base class for {@link QueryLookupStrategy} implementations that need access to an {@link EntityManager}.
50      *
51      * @author Oliver Gierke
52      * @author Thomas Darimont
53      */

54     private abstract static class AbstractQueryLookupStrategy implements QueryLookupStrategy {
55
56         private final EntityManager em;
57         private final JpaQueryMethodFactory queryMethodFactory;
58
59         /**
60          * Creates a new {@link AbstractQueryLookupStrategy}.
61          *
62          * @param em must not be {@literal null}.
63          * @param queryMethodFactory must not be {@literal null}.
64          */

65         public AbstractQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory) {
66
67             Assert.notNull(em, "EntityManager must not be null!");
68             Assert.notNull(queryMethodFactory, "JpaQueryMethodFactory must not be null!");
69
70             this.em = em;
71             this.queryMethodFactory = queryMethodFactory;
72         }
73
74         /*
75          * (non-Javadoc)
76          * @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries)
77          */

78         @Override
79         public final RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
80                 NamedQueries namedQueries) {
81             return resolveQuery(queryMethodFactory.build(method, metadata, factory), em, namedQueries);
82         }
83
84         protected abstract RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries);
85     }
86
87     /**
88      * {@link QueryLookupStrategy} to create a query from the method name.
89      *
90      * @author Oliver Gierke
91      * @author Thomas Darimont
92      */

93     private static class CreateQueryLookupStrategy extends AbstractQueryLookupStrategy {
94
95         private final EscapeCharacter escape;
96
97         public CreateQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
98                 EscapeCharacter escape) {
99
100             super(em, queryMethodFactory);
101
102             this.escape = escape;
103         }
104
105         @Override
106         protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) {
107             return new PartTreeJpaQuery(method, em, escape);
108         }
109     }
110
111     /**
112      * {@link QueryLookupStrategy} that tries to detect a declared query declared via {@link Query} annotation followed by
113      * a JPA named query lookup.
114      *
115      * @author Oliver Gierke
116      * @author Thomas Darimont
117      * @author Jens Schauder
118      */

119     private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy {
120
121         private final QueryMethodEvaluationContextProvider evaluationContextProvider;
122
123         /**
124          * Creates a new {@link DeclaredQueryLookupStrategy}.
125          *
126          * @param em
127          * @param extractor
128          * @param queryMethodFactory
129          * @param evaluationContextProvider
130          */

131         public DeclaredQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
132                 QueryMethodEvaluationContextProvider evaluationContextProvider) {
133
134             super(em, queryMethodFactory);
135
136             this.evaluationContextProvider = evaluationContextProvider;
137         }
138
139         /*
140          * (non-Javadoc)
141          * @see org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy.AbstractQueryLookupStrategy#resolveQuery(org.springframework.data.jpa.repository.query.JpaQueryMethod, javax.persistence.EntityManager, org.springframework.data.repository.core.NamedQueries)
142          */

143         @Override
144         protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) {
145
146             RepositoryQuery query = JpaQueryFactory.INSTANCE.fromQueryAnnotation(method, em, evaluationContextProvider);
147
148             if (null != query) {
149                 return query;
150             }
151
152             query = JpaQueryFactory.INSTANCE.fromProcedureAnnotation(method, em);
153
154             if (null != query) {
155                 return query;
156             }
157
158             String name = method.getNamedQueryName();
159             if (namedQueries.hasQuery(name)) {
160                 return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name),
161                         evaluationContextProvider);
162             }
163
164             query = NamedQuery.lookupFrom(method, em);
165
166             if (null != query) {
167                 return query;
168             }
169
170             throw new IllegalStateException(
171                     String.format("Did neither find a NamedQuery nor an annotated query for method %s!", method));
172         }
173     }
174
175     /**
176      * {@link QueryLookupStrategy} to try to detect a declared query first (
177      * {@link org.springframework.data.jpa.repository.Query}, JPA named query). In case none is found we fall back on
178      * query creation.
179      *
180      * @author Oliver Gierke
181      * @author Thomas Darimont
182      */

183     private static class CreateIfNotFoundQueryLookupStrategy extends AbstractQueryLookupStrategy {
184
185         private final DeclaredQueryLookupStrategy lookupStrategy;
186         private final CreateQueryLookupStrategy createStrategy;
187
188         /**
189          * Creates a new {@link CreateIfNotFoundQueryLookupStrategy}.
190          *
191          * @param em must not be {@literal null}.
192          * @param queryMethodFactory must not be {@literal null}.
193          * @param createStrategy must not be {@literal null}.
194          * @param lookupStrategy must not be {@literal null}.
195          */

196         public CreateIfNotFoundQueryLookupStrategy(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
197                 CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy) {
198
199             super(em, queryMethodFactory);
200
201             Assert.notNull(createStrategy, "CreateQueryLookupStrategy must not be null!");
202             Assert.notNull(lookupStrategy, "DeclaredQueryLookupStrategy must not be null!");
203
204             this.createStrategy = createStrategy;
205             this.lookupStrategy = lookupStrategy;
206         }
207
208         /*
209          * (non-Javadoc)
210          * @see org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy.AbstractQueryLookupStrategy#resolveQuery(org.springframework.data.jpa.repository.query.JpaQueryMethod, javax.persistence.EntityManager, org.springframework.data.repository.core.NamedQueries)
211          */

212         @Override
213         protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) {
214
215             try {
216                 return lookupStrategy.resolveQuery(method, em, namedQueries);
217             } catch (IllegalStateException e) {
218                 return createStrategy.resolveQuery(method, em, namedQueries);
219             }
220         }
221     }
222
223     /**
224      * Creates a {@link QueryLookupStrategy} for the given {@link EntityManager} and {@link Key}.
225      *
226      * @param em must not be {@literal null}.
227      * @param queryMethodFactory must not be {@literal null}.
228      * @param key may be {@literal null}.
229      * @param evaluationContextProvider must not be {@literal null}.
230      * @param escape
231      * @param extractor must not be {@literal null}.
232      * @return
233      */

234     public static QueryLookupStrategy create(EntityManager em, JpaQueryMethodFactory queryMethodFactory,
235             @Nullable Key key, QueryMethodEvaluationContextProvider evaluationContextProvider, EscapeCharacter escape) {
236
237         Assert.notNull(em, "EntityManager must not be null!");
238         Assert.notNull(evaluationContextProvider, "EvaluationContextProvider must not be null!");
239
240         switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) {
241             case CREATE:
242                 return new CreateQueryLookupStrategy(em, queryMethodFactory, escape);
243             case USE_DECLARED_QUERY:
244                 return new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider);
245             case CREATE_IF_NOT_FOUND:
246                 return new CreateIfNotFoundQueryLookupStrategy(em, queryMethodFactory,
247                         new CreateQueryLookupStrategy(em, queryMethodFactory, escape),
248                         new DeclaredQueryLookupStrategy(em, queryMethodFactory, evaluationContextProvider));
249             default:
250                 throw new IllegalArgumentException(String.format("Unsupported query lookup strategy %s!", key));
251         }
252     }
253 }
254