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 @Deprecated
59 public ProviderSqlSource(Configuration configuration, Object provider) {
60 this(configuration, provider, null, null);
61 }
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 @Deprecated
78 public ProviderSqlSource(Configuration configuration, Object provider, Class<?> mapperType, Method mapperMethod) {
79 this(configuration, (Annotation) provider, mapperType, mapperMethod);
80 }
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 public ProviderSqlSource(Configuration configuration, Annotation provider, Class<?> mapperType, Method mapperMethod) {
96 String candidateProviderMethodName;
97 Method candidateProviderMethod = null;
98 try {
99 this.configuration = configuration;
100 this.mapperMethod = mapperMethod;
101 Lang lang = mapperMethod == null ? null : mapperMethod.getAnnotation(Lang.class);
102 this.languageDriver = configuration.getLanguageDriver(lang == null ? null : lang.value());
103 this.providerType = getProviderType(configuration, provider, mapperMethod);
104 candidateProviderMethodName = (String) provider.annotationType().getMethod("method").invoke(provider);
105
106 if (candidateProviderMethodName.length() == 0 && ProviderMethodResolver.class.isAssignableFrom(this.providerType)) {
107 candidateProviderMethod = ((ProviderMethodResolver) this.providerType.getDeclaredConstructor().newInstance())
108 .resolveMethod(new ProviderContext(mapperType, mapperMethod, configuration.getDatabaseId()));
109 }
110 if (candidateProviderMethod == null) {
111 candidateProviderMethodName = candidateProviderMethodName.length() == 0 ? "provideSql" : candidateProviderMethodName;
112 for (Method m : this.providerType.getMethods()) {
113 if (candidateProviderMethodName.equals(m.getName()) && CharSequence.class.isAssignableFrom(m.getReturnType())) {
114 if (candidateProviderMethod != null) {
115 throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
116 + candidateProviderMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName()
117 + "'. Sql provider method can not overload.");
118 }
119 candidateProviderMethod = m;
120 }
121 }
122 }
123 } catch (BuilderException e) {
124 throw e;
125 } catch (Exception e) {
126 throw new BuilderException("Error creating SqlSource for SqlProvider. Cause: " + e, e);
127 }
128 if (candidateProviderMethod == null) {
129 throw new BuilderException("Error creating SqlSource for SqlProvider. Method '"
130 + candidateProviderMethodName + "' not found in SqlProvider '" + this.providerType.getName() + "'.");
131 }
132 this.providerMethod = candidateProviderMethod;
133 this.providerMethodArgumentNames = new ParamNameResolver(configuration, this.providerMethod).getNames();
134 this.providerMethodParameterTypes = this.providerMethod.getParameterTypes();
135
136 ProviderContext candidateProviderContext = null;
137 Integer candidateProviderContextIndex = null;
138 for (int i = 0; i < this.providerMethodParameterTypes.length; i++) {
139 Class<?> parameterType = this.providerMethodParameterTypes[i];
140 if (parameterType == ProviderContext.class) {
141 if (candidateProviderContext != null) {
142 throw new BuilderException("Error creating SqlSource for SqlProvider. ProviderContext found multiple in SqlProvider method ("
143 + this.providerType.getName() + "." + providerMethod.getName()
144 + "). ProviderContext can not define multiple in SqlProvider method argument.");
145 }
146 candidateProviderContext = new ProviderContext(mapperType, mapperMethod, configuration.getDatabaseId());
147 candidateProviderContextIndex = i;
148 }
149 }
150 this.providerContext = candidateProviderContext;
151 this.providerContextIndex = candidateProviderContextIndex;
152 }
153
154 @Override
155 public BoundSql getBoundSql(Object parameterObject) {
156 SqlSource sqlSource = createSqlSource(parameterObject);
157 return sqlSource.getBoundSql(parameterObject);
158 }
159
160 private SqlSource createSqlSource(Object parameterObject) {
161 try {
162 String sql;
163 if (parameterObject instanceof Map) {
164 int bindParameterCount = providerMethodParameterTypes.length - (providerContext == null ? 0 : 1);
165 if (bindParameterCount == 1
166 && providerMethodParameterTypes[Integer.valueOf(0).equals(providerContextIndex) ? 1 : 0].isAssignableFrom(parameterObject.getClass())) {
167 sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject));
168 } else {
169 @SuppressWarnings("unchecked")
170 Map<String, Object> params = (Map<String, Object>) parameterObject;
171 sql = invokeProviderMethod(extractProviderMethodArguments(params, providerMethodArgumentNames));
172 }
173 } else if (providerMethodParameterTypes.length == 0) {
174 sql = invokeProviderMethod();
175 } else if (providerMethodParameterTypes.length == 1) {
176 if (providerContext == null) {
177 sql = invokeProviderMethod(parameterObject);
178 } else {
179 sql = invokeProviderMethod(providerContext);
180 }
181 } else if (providerMethodParameterTypes.length == 2) {
182 sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject));
183 } else {
184 throw new BuilderException("Cannot invoke SqlProvider method '" + providerMethod
185 + "' with specify parameter '" + (parameterObject == null ? null : parameterObject.getClass())
186 + "' because SqlProvider method arguments for '" + mapperMethod + "' is an invalid combination.");
187 }
188 Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
189 return languageDriver.createSqlSource(configuration, sql, parameterType);
190 } catch (BuilderException e) {
191 throw e;
192 } catch (Exception e) {
193 throw new BuilderException("Error invoking SqlProvider method '" + providerMethod
194 + "' with specify parameter '" + (parameterObject == null ? null : parameterObject.getClass()) + "'. Cause: " + extractRootCause(e), e);
195 }
196 }
197
198 private Throwable extractRootCause(Exception e) {
199 Throwable cause = e;
200 while (cause.getCause() != null) {
201 cause = cause.getCause();
202 }
203 return cause;
204 }
205
206 private Object[] extractProviderMethodArguments(Object parameterObject) {
207 if (providerContext != null) {
208 Object[] args = new Object[2];
209 args[providerContextIndex == 0 ? 1 : 0] = parameterObject;
210 args[providerContextIndex] = providerContext;
211 return args;
212 } else {
213 return new Object[] { parameterObject };
214 }
215 }
216
217 private Object[] extractProviderMethodArguments(Map<String, Object> params, String[] argumentNames) {
218 Object[] args = new Object[argumentNames.length];
219 for (int i = 0; i < args.length; i++) {
220 if (providerContextIndex != null && providerContextIndex == i) {
221 args[i] = providerContext;
222 } else {
223 args[i] = params.get(argumentNames[i]);
224 }
225 }
226 return args;
227 }
228
229 private String invokeProviderMethod(Object... args) throws Exception {
230 Object targetObject = null;
231 if (!Modifier.isStatic(providerMethod.getModifiers())) {
232 targetObject = providerType.getDeclaredConstructor().newInstance();
233 }
234 CharSequence sql = (CharSequence) providerMethod.invoke(targetObject, args);
235 return sql != null ? sql.toString() : null;
236 }
237
238 private Class<?> getProviderType(Configuration configuration, Annotation providerAnnotation, Method mapperMethod)
239 throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
240 Class<?> type = (Class<?>) providerAnnotation.annotationType().getMethod("type").invoke(providerAnnotation);
241 Class<?> value = (Class<?>) providerAnnotation.annotationType().getMethod("value").invoke(providerAnnotation);
242 if (value == void.class && type == void.class) {
243 if (configuration.getDefaultSqlProviderType() != null) {
244 return configuration.getDefaultSqlProviderType();
245 }
246 throw new BuilderException("Please specify either 'value' or 'type' attribute of @"
247 + providerAnnotation.annotationType().getSimpleName()
248 + " at the '" + mapperMethod.toString() + "'.");
249 }
250 if (value != void.class && type != void.class && value != type) {
251 throw new BuilderException("Cannot specify different class on 'value' and 'type' attribute of @"
252 + providerAnnotation.annotationType().getSimpleName()
253 + " at the '" + mapperMethod.toString() + "'.");
254 }
255 return value == void.class ? type : value;
256 }
257
258 }