1 /*
2  * Copyright 2014-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 import java.util.ArrayList;
20 import java.util.Arrays;
21 import java.util.List;
22
23 import javax.persistence.NamedStoredProcedureQueries;
24 import javax.persistence.NamedStoredProcedureQuery;
25 import javax.persistence.StoredProcedureParameter;
26
27 import org.springframework.core.annotation.AnnotatedElementUtils;
28 import org.springframework.lang.Nullable;
29 import org.springframework.util.Assert;
30 import org.springframework.util.StringUtils;
31
32 /**
33  * A factory class for {@link StoredProcedureAttributes}.
34  *
35  * @author Thomas Darimont
36  * @author Oliver Gierke
37  * @author Christoph Strobl
38  * @author Mark Paluch
39  * @author Diego Diez
40  * @author Jeff Sheets
41  * @since 1.6
42  */

43 enum StoredProcedureAttributeSource {
44
45     INSTANCE;
46
47     /**
48      * Creates a new {@link StoredProcedureAttributes} from the given {@link Method} and {@link JpaEntityMetadata}.
49      *
50      * @param method must not be {@literal null}
51      * @param entityMetadata must not be {@literal null}
52      * @return
53      */

54     public StoredProcedureAttributes createFrom(Method method, JpaEntityMetadata<?> entityMetadata) {
55
56         Assert.notNull(method, "Method must not be null!");
57         Assert.notNull(entityMetadata, "EntityMetadata must not be null!");
58
59         Procedure procedure = AnnotatedElementUtils.findMergedAnnotation(method, Procedure.class);
60         Assert.notNull(procedure, "Method must have an @Procedure annotation!");
61
62         NamedStoredProcedureQuery namedStoredProc = tryFindAnnotatedNamedStoredProcedureQuery(method, entityMetadata,
63                 procedure);
64
65         if (namedStoredProc != null) {
66             return newProcedureAttributesFrom(method, namedStoredProc, procedure);
67         }
68
69         String procedureName = deriveProcedureNameFrom(method, procedure);
70         if (StringUtils.isEmpty(procedureName)) {
71             throw new IllegalArgumentException("Could not determine name of procedure for @Procedure annotated method: "
72                     + method);
73         }
74
75         return new StoredProcedureAttributes(procedureName, procedure.outputParameterName(), method.getReturnType());
76     }
77
78     /**
79      * Tries to derive the procedure name from the given {@link Procedure}, falls back to the name of the given
80      * {@link Method}.
81      *
82      * @param method
83      * @param procedure
84      * @return
85      */

86     private String deriveProcedureNameFrom(Method method, Procedure procedure) {
87
88         if (StringUtils.hasText(procedure.value())) {
89             return procedure.value();
90         }
91
92         String procedureName = procedure.procedureName();
93         return StringUtils.hasText(procedureName) ? procedureName : method.getName();
94     }
95
96     /**
97      * @param method
98      * @param namedStoredProc
99      * @param procedure
100      * @return
101      */

102     private StoredProcedureAttributes newProcedureAttributesFrom(Method method,
103             NamedStoredProcedureQuery namedStoredProc, Procedure procedure) {
104
105         List<String> outputParameterNames = new ArrayList<>();
106         List<Class<?>> outputParameterTypes = new ArrayList<>();
107
108         if (!procedure.outputParameterName().isEmpty()) {
109             // we give the output parameter definition from the @Procedure annotation precedence
110             outputParameterNames.add(procedure.outputParameterName());
111         } else {
112
113             // try to discover the output parameter
114             List<StoredProcedureParameter> outputParameters = extractOutputParametersFrom(namedStoredProc);
115
116             for (StoredProcedureParameter outputParameter : outputParameters) {
117                 outputParameterNames.add(outputParameter.name());
118                 outputParameterTypes.add(outputParameter.type());
119             }
120         }
121
122         if (outputParameterTypes.isEmpty()) {
123             outputParameterTypes.add(method.getReturnType());
124         }
125
126         return new StoredProcedureAttributes(namedStoredProc.name(), outputParameterNames, outputParameterTypes, true);
127     }
128
129     private List<StoredProcedureParameter> extractOutputParametersFrom(NamedStoredProcedureQuery namedStoredProc) {
130
131         List<StoredProcedureParameter> outputParameters = new ArrayList<StoredProcedureParameter>();
132
133         for (StoredProcedureParameter param : namedStoredProc.parameters()) {
134
135             switch (param.mode()) {
136                 case OUT:
137                 case INOUT:
138                 case REF_CURSOR:
139                     outputParameters.add(param);
140                     break;
141                 case IN:
142                 default:
143                     continue;
144             }
145         }
146
147         return outputParameters;
148     }
149
150     /**
151      * @param method must not be {@literal null}.
152      * @param entityMetadata must not be {@literal null}.
153      * @param procedure must not be {@literal null}.
154      * @return
155      */

156     @Nullable
157     private NamedStoredProcedureQuery tryFindAnnotatedNamedStoredProcedureQuery(Method method,
158             JpaEntityMetadata<?> entityMetadata, Procedure procedure) {
159
160         Assert.notNull(method, "Method must not be null!");
161         Assert.notNull(entityMetadata, "EntityMetadata must not be null!");
162         Assert.notNull(procedure, "Procedure must not be null!");
163
164         Class<?> entityType = entityMetadata.getJavaType();
165
166         List<NamedStoredProcedureQuery> queries = collectNamedStoredProcedureQueriesFrom(entityType);
167
168         if (queries.isEmpty()) {
169             return null;
170         }
171
172         String namedProcedureName = derivedNamedProcedureNameFrom(method, entityMetadata, procedure);
173
174         for (NamedStoredProcedureQuery query : queries) {
175
176             if (query.name().equals(namedProcedureName)) {
177                 return query;
178             }
179         }
180
181         return null;
182     }
183
184     /**
185      * @param method
186      * @param entityMetadata
187      * @param procedure
188      * @return
189      */

190     private String derivedNamedProcedureNameFrom(Method method, JpaEntityMetadata<?> entityMetadata, Procedure procedure) {
191         return StringUtils.hasText(procedure.name()) ? procedure.name() : entityMetadata.getEntityName() + "."
192                 + method.getName();
193     }
194
195     /**
196      * @param entityType
197      * @return
198      */

199     private List<NamedStoredProcedureQuery> collectNamedStoredProcedureQueriesFrom(Class<?> entityType) {
200
201         List<NamedStoredProcedureQuery> queries = new ArrayList<NamedStoredProcedureQuery>();
202
203         NamedStoredProcedureQueries namedQueriesAnnotation = AnnotatedElementUtils.findMergedAnnotation(entityType,
204                 NamedStoredProcedureQueries.class);
205         if (namedQueriesAnnotation != null) {
206             queries.addAll(Arrays.asList(namedQueriesAnnotation.value()));
207         }
208
209         NamedStoredProcedureQuery namedQueryAnnotation = AnnotatedElementUtils.findMergedAnnotation(entityType,
210                 NamedStoredProcedureQuery.class);
211         if (namedQueryAnnotation != null) {
212             queries.add(namedQueryAnnotation);
213         }
214
215         return queries;
216     }
217 }
218