1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.apache.ibatis.builder.annotation;
17
18 import java.lang.annotation.Annotation;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.lang.reflect.Modifier;
22 import java.util.Map;
23
24 import org.apache.ibatis.annotations.Lang;
25 import org.apache.ibatis.builder.BuilderException;
26 import org.apache.ibatis.mapping.BoundSql;
27 import org.apache.ibatis.mapping.SqlSource;
28 import org.apache.ibatis.reflection.ParamNameResolver;
29 import org.apache.ibatis.scripting.LanguageDriver;
30 import org.apache.ibatis.session.Configuration;
31
32
33
34
35
36 public class ProviderSqlSource implements SqlSource {
37
38 private final Configuration configuration;
39 private final Class<?> providerType;
40 private final LanguageDriver languageDriver;
41 private final Method mapperMethod;
42 private final Method providerMethod;
43 private final String[] providerMethodArgumentNames;
44 private final Class<?>[] providerMethodParameterTypes;
45 private final ProviderContext providerContext;
46 private final Integer providerContextIndex;
47
48
49
50
51
52
53
54
55
56
57
58
59 @Deprecated
60 public ProviderSqlSource(Configuration configuration, Object provider) {
61 this(configuration, provider, null, null);
62 }
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 @Deprecated
82 public ProviderSqlSource(Configuration configuration, Object provider, Class<?> mapperType, Method mapperMethod) {
83 this(configuration, (Annotation) provider, mapperType, mapperMethod);
84 }
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 public ProviderSqlSource(Configuration configuration, Annotation provider, Class<?> mapperType, Method mapperMethod) {
101 String candidateProviderMethodName;
102 Method candidateProviderMethod = null;
103 try {
104 this.configuration = configuration;
105 this.mapperMethod = mapperMethod;
106 Lang lang = mapperMethod == null ? null : mapperMethod.getAnnotation(Lang.class);
107 this.languageDriver = configuration.getLanguageDriver(lang == null ? null : lang.value());
108 this.providerType = getProviderType(configuration, provider, mapperMethod);
109 candidateProviderMethodName = (String) provider.annotationType().getMethod("method").invoke(provider);
110
111 if (candidateProviderMethodName.length() == 0
112 && ProviderMethodResolver.class.isAssignableFrom(this.providerType)) {
113 candidateProviderMethod = ((ProviderMethodResolver) this.providerType.getDeclaredConstructor().newInstance())
114 .resolveMethod(new ProviderContext(mapperType, mapperMethod, configuration.getDatabaseId()));
115 }
116 if (candidateProviderMethod == null) {
117 candidateProviderMethodName = candidateProviderMethodName.length() == 0 ? "provideSql"
118 : candidateProviderMethodName;
119 for (Method m : this.providerType.getMethods()) {
120 if (candidateProviderMethodName.equals(m.getName())
121 && CharSequence.class.isAssignableFrom(m.getReturnType())) {
122 if (candidateProviderMethod != null) {
123 throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
124 + candidateProviderMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName()
125 + "'. Sql provider method can not overload.");
126 }
127 candidateProviderMethod = m;
128 }
129 }
130 }
131 } catch (BuilderException e) {
132 throw e;
133 } catch (Exception e) {
134 throw new BuilderException("Error creating SqlSource for SqlProvider. Cause: " + e, e);
135 }
136 if (candidateProviderMethod == null) {
137 throw new BuilderException("Error creating SqlSource for SqlProvider. Method '" + candidateProviderMethodName
138 + "' not found in SqlProvider '" + this.providerType.getName() + "'.");
139 }
140 this.providerMethod = candidateProviderMethod;
141 this.providerMethodArgumentNames = new ParamNameResolver(configuration, this.providerMethod).getNames();
142 this.providerMethodParameterTypes = this.providerMethod.getParameterTypes();
143
144 ProviderContext candidateProviderContext = null;
145 Integer candidateProviderContextIndex = null;
146 for (int i = 0; i < this.providerMethodParameterTypes.length; i++) {
147 Class<?> parameterType = this.providerMethodParameterTypes[i];
148 if (parameterType == ProviderContext.class) {
149 if (candidateProviderContext != null) {
150 throw new BuilderException(
151 "Error creating SqlSource for SqlProvider. ProviderContext found multiple in SqlProvider method ("
152 + this.providerType.getName() + "." + providerMethod.getName()
153 + "). ProviderContext can not define multiple in SqlProvider method argument.");
154 }
155 candidateProviderContext = new ProviderContext(mapperType, mapperMethod, configuration.getDatabaseId());
156 candidateProviderContextIndex = i;
157 }
158 }
159 this.providerContext = candidateProviderContext;
160 this.providerContextIndex = candidateProviderContextIndex;
161 }
162
163 @Override
164 public BoundSql getBoundSql(Object parameterObject) {
165 SqlSource sqlSource = createSqlSource(parameterObject);
166 return sqlSource.getBoundSql(parameterObject);
167 }
168
169 private SqlSource createSqlSource(Object parameterObject) {
170 try {
171 String sql;
172 if (parameterObject instanceof Map) {
173 int bindParameterCount = providerMethodParameterTypes.length - (providerContext == null ? 0 : 1);
174 if (bindParameterCount == 1
175 && providerMethodParameterTypes[Integer.valueOf(0).equals(providerContextIndex) ? 1 : 0]
176 .isAssignableFrom(parameterObject.getClass())) {
177 sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject));
178 } else {
179 @SuppressWarnings("unchecked")
180 Map<String, Object> params = (Map<String, Object>) parameterObject;
181 sql = invokeProviderMethod(extractProviderMethodArguments(params, providerMethodArgumentNames));
182 }
183 } else
184 switch (providerMethodParameterTypes.length) {
185 case 0:
186 sql = invokeProviderMethod();
187 break;
188 case 1:
189 if (providerContext == null) {
190 sql = invokeProviderMethod(parameterObject);
191 } else {
192 sql = invokeProviderMethod(providerContext);
193 }
194 break;
195 case 2:
196 sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject));
197 break;
198 default:
199 throw new BuilderException("Cannot invoke SqlProvider method '" + providerMethod
200 + "' with specify parameter '" + (parameterObject == null ? null : parameterObject.getClass())
201 + "' because SqlProvider method arguments for '" + mapperMethod + "' is an invalid combination.");
202 }
203 Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
204 return languageDriver.createSqlSource(configuration, sql, parameterType);
205 } catch (BuilderException e) {
206 throw e;
207 } catch (Exception e) {
208 throw new BuilderException("Error invoking SqlProvider method '" + providerMethod + "' with specify parameter '"
209 + (parameterObject == null ? null : parameterObject.getClass()) + "'. Cause: " + extractRootCause(e), e);
210 }
211 }
212
213 private Throwable extractRootCause(Exception e) {
214 Throwable cause = e;
215 while (cause.getCause() != null) {
216 cause = cause.getCause();
217 }
218 return cause;
219 }
220
221 private Object[] extractProviderMethodArguments(Object parameterObject) {
222 if (providerContext != null) {
223 Object[] args = new Object[2];
224 args[providerContextIndex == 0 ? 1 : 0] = parameterObject;
225 args[providerContextIndex] = providerContext;
226 return args;
227 }
228 return new Object[] { parameterObject };
229 }
230
231 private Object[] extractProviderMethodArguments(Map<String, Object> params, String[] argumentNames) {
232 Object[] args = new Object[argumentNames.length];
233 for (int i = 0; i < args.length; i++) {
234 if (providerContextIndex != null && providerContextIndex == i) {
235 args[i] = providerContext;
236 } else {
237 args[i] = params.get(argumentNames[i]);
238 }
239 }
240 return args;
241 }
242
243 private String invokeProviderMethod(Object... args) throws Exception {
244 Object targetObject = null;
245 if (!Modifier.isStatic(providerMethod.getModifiers())) {
246 targetObject = providerType.getDeclaredConstructor().newInstance();
247 }
248 CharSequence sql = (CharSequence) providerMethod.invoke(targetObject, args);
249 return sql != null ? sql.toString() : null;
250 }
251
252 private Class<?> getProviderType(Configuration configuration, Annotation providerAnnotation, Method mapperMethod)
253 throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
254 Class<?> type = (Class<?>) providerAnnotation.annotationType().getMethod("type").invoke(providerAnnotation);
255 Class<?> value = (Class<?>) providerAnnotation.annotationType().getMethod("value").invoke(providerAnnotation);
256 if (value == void.class && type == void.class) {
257 if (configuration.getDefaultSqlProviderType() != null) {
258 return configuration.getDefaultSqlProviderType();
259 }
260 throw new BuilderException("Please specify either 'value' or 'type' attribute of @"
261 + providerAnnotation.annotationType().getSimpleName() + " at the '" + mapperMethod.toString() + "'.");
262 }
263 if (value != void.class && type != void.class && value != type) {
264 throw new BuilderException("Cannot specify different class on 'value' and 'type' attribute of @"
265 + providerAnnotation.annotationType().getSimpleName() + " at the '" + mapperMethod.toString() + "'.");
266 }
267 return value == void.class ? type : value;
268 }
269
270 }