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