1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package net.sf.autodao.impl;
19
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.Method;
22 import java.util.Collection;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27
28 import net.sf.autodao.Finder;
29 import net.sf.autodao.Named;
30 import net.sf.autodao.QueryArgumentTransformer;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33 import org.springframework.core.annotation.AnnotationUtils;
34 import static org.springframework.util.StringUtils.hasText;
35
36
37
38
39
40
41 public abstract class ParametersChecker implements Utils.ParameterCallback {
42 private final boolean singleFind;
43 @NotNull
44 private final Method method;
45 @NotNull
46 private final Map<Class<?>, QueryArgumentTransformer<?, ?>> transformers;
47
48 @NotNull
49 private final Set<String> names = new HashSet<>();
50 private boolean limit;
51 private boolean offset;
52 private int indexed;
53
54 protected ParametersChecker(@NotNull final Method method,
55 @NotNull final Map<Class<?>, QueryArgumentTransformer<?, ?>> transformers) {
56 final Finder f = AnnotationUtils.getAnnotation(method, Finder.class);
57 if (f == null)
58 throw new SpecificationViolationException("Method is neither a finder method nor is implemented in AutoDAO", method);
59
60 final int sum
61 = (hasText(f.query()) ? 1 : 0)
62 + (hasText(f.queryName()) ? 1 : 0)
63 + (hasText(f.sqlQuery()) ? 1 : 0)
64 + (hasText(f.sqlQueryName()) ? 1 : 0);
65 if (sum < 1)
66 throw new SpecificationViolationException("You need to specify at least one of: query, queryName, sqlQuery, sqlQueryName", method);
67
68 if (sum > 1)
69 throw new SpecificationViolationException("You can specify only one of: query, queryName, sqlQuery, sqlQueryName", method);
70
71 this.singleFind = validateReturnType(method);
72 this.method = method;
73 this.transformers = transformers;
74 }
75
76 @NotNull
77 protected final Method getMethod() {
78 return method;
79 }
80
81 @Nullable
82 protected abstract Class<?> getExpectedType(int paramIndex);
83
84 @Nullable
85 protected abstract Class<?> getExpectedType(@NotNull String paramName);
86
87 protected abstract int getExpectedOrdinalParametersCount();
88
89 @Nullable
90 protected abstract Set<String> getExpectedNamedParameters();
91
92
93
94
95
96
97 private static boolean validateReturnType(@NotNull final Method method) {
98 final Finder f = AnnotationUtils.findAnnotation(method, Finder.class);
99 final boolean singleFind = !Collection.class.isAssignableFrom(method.getReturnType());
100 if (!singleFind) {
101 final Class<?> returnType = method.getReturnType();
102 if (!returnType.isAssignableFrom(f.returnAs())) {
103 throw new SpecificationViolationException(
104 "@Finder.returnAs value (" + f.returnAs().getName()
105 + ") cannot be cast to method return type (" + returnType.getName() + ")", method
106 );
107 } else if (f.returnAs() != List.class) {
108 if (f.returnAs().isInterface()) {
109 throw new SpecificationViolationException(
110 "@Finder.returnAs value (" + f.returnAs().getName() + ") cannot be an interface", method
111 );
112 } else {
113 try {
114 f.returnAs().getConstructor();
115 } catch (NoSuchMethodException e) {
116 throw new SpecificationViolationException(
117 "@Finder.returnAs value (" + f.returnAs().getName() + ") doesn't have no-arg constructor.", method
118 );
119 }
120 }
121 }
122 }
123 return singleFind;
124 }
125
126 public void check() {
127 Utils.visitMethodParameters(method, this);
128
129
130 int queryIndexedCount = getExpectedOrdinalParametersCount();
131 if (queryIndexedCount >= 0 && indexed != queryIndexedCount)
132 throw new SpecificationViolationException(String.format("Not enough indexed params (query has %s but method has %s)", queryIndexedCount, indexed), method);
133
134 final Set<?> expectedNamedParameters = getExpectedNamedParameters();
135 if (expectedNamedParameters != null && !names.equals(expectedNamedParameters))
136 throw new SpecificationViolationException(String.format("Wrong named parameters (query has %s but method has %s)", expectedNamedParameters, names), method);
137 }
138
139 @Override
140 public void visit(final int index, @NotNull final Class<?> type, @NotNull final Annotation[] annotations) {
141 boolean processed = false;
142 if (Utils.getLimit(annotations) != null) {
143 limitCheck(type);
144 processed = true;
145 }
146 if (Utils.getOffset(annotations) != null) {
147 offsetCheck(type);
148 processed = true;
149 }
150 final Named named = Utils.getNamed(annotations);
151 if (processed) {
152 if (named != null)
153 throw new SpecificationViolationException("@Named cannot be present on @Limit/@Offset param", method);
154 } else {
155 if (named == null) {
156 if (!names.isEmpty())
157 throw new SpecificationViolationException("You're not allowed to mix @Named and indexed params", method);
158
159 final int paramIndex = index + 1
160 - (limit ? 1 : 0)
161 - (offset ? 1 : 0);
162 final Class<?> expectedType = getExpectedType(paramIndex);
163 if (expectedType != null) {
164 final Class<?> actualType = getActualParameterType(type, false);
165 if (!expectedType.isAssignableFrom(actualType)) {
166 final String msg = String.format("Indexed parameter '%s' of type %s cannot be assigned to query parameter of type %s", paramIndex, actualType.getName(), expectedType.getName());
167 throw new SpecificationViolationException(msg, method);
168 }
169 }
170 indexed++;
171 } else {
172 if (indexed > 0)
173 throw new SpecificationViolationException("You're not allowed to mix @Named and indexed params", method);
174
175 final String name = named.value();
176 final Class<?> expectedType = getExpectedType(name);
177 if (expectedType != null) {
178 final Class<?> actualClass = getActualParameterType(type, true);
179 if (!Collection.class.isAssignableFrom(actualClass) && !expectedType.isAssignableFrom(actualClass)) {
180 final String msg = String.format("Named parameter '%s' of type %s cannot be assigned to query parameter of type %s", name, actualClass.getName(), expectedType.getName());
181 throw new SpecificationViolationException(msg, method);
182 }
183 }
184 if (!names.add(name)) {
185 throw new SpecificationViolationException("Duplicate named parameters '" + name + "'", method);
186 }
187 }
188 }
189 }
190
191 @NotNull
192 private Class<?> getActualParameterType(@NotNull final Class<?> declaredType, final boolean handleArrays) {
193 final QueryArgumentTransformer<?, ?> transformer = transformers.get(declaredType);
194 final Class<?> transformedType = transformer == null ? declaredType : transformer.getTargetType();
195
196 final Class<?> arrayUnwrappedType = handleArrays && transformedType.isArray() ? transformedType.getComponentType() : transformedType;
197 return primitive(arrayUnwrappedType);
198 }
199
200
201
202
203
204
205
206
207
208 @SuppressWarnings({ "IfStatementWithTooManyBranches", "ObjectEquality" })
209 @NotNull
210 private static Class<?> primitive(@NotNull final Class<?> type) {
211 if (!type.isPrimitive()) {
212 return type;
213 } else if (type == Integer.TYPE) {
214 return Integer.class;
215 } else if (type == Double.TYPE) {
216 return Double.class;
217 } else if (type == Long.TYPE) {
218 return Long.class;
219 } else if (type == Boolean.TYPE) {
220 return Boolean.class;
221 } else if (type == Float.TYPE) {
222 return Float.class;
223 } else if (type == Short.TYPE) {
224 return Short.class;
225 } else if (type == Byte.TYPE) {
226 return Byte.class;
227 } else if (type == Character.TYPE) {
228 return Character.class;
229 } else {
230 return type;
231 }
232 }
233
234 private void limitCheck(final Class<?> type) {
235 limitOffsetCheck(limit, type);
236 limit = true;
237 }
238
239 private void offsetCheck(final Class<?> type) {
240 limitOffsetCheck(offset, type);
241 offset = true;
242 }
243
244 private void limitOffsetCheck(final boolean value, final Class<?> type) {
245 if (value)
246 throw new SpecificationViolationException("@Limit/@Offset can appear only on one parameter", method);
247
248 if (singleFind)
249 throw new SpecificationViolationException("@Limit/@Offset isn't allowed on single-object finders", method);
250
251 if (type != Integer.class && type != Integer.TYPE)
252 throw new SpecificationViolationException("@Limit and @Offset argument type must be either int or java.lang.Integer", method);
253 }
254 }