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.io.IOException;
19 import java.io.InputStream;
20 import java.lang.annotation.Annotation;
21 import java.lang.reflect.Array;
22 import java.lang.reflect.GenericArrayType;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.ParameterizedType;
25 import java.lang.reflect.Type;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.HashMap;
29 import java.util.HashSet;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Locale;
33 import java.util.Map;
34 import java.util.Set;
35
36 import org.apache.ibatis.annotations.Arg;
37 import org.apache.ibatis.annotations.CacheNamespace;
38 import org.apache.ibatis.annotations.CacheNamespaceRef;
39 import org.apache.ibatis.annotations.Case;
40 import org.apache.ibatis.annotations.ConstructorArgs;
41 import org.apache.ibatis.annotations.Delete;
42 import org.apache.ibatis.annotations.DeleteProvider;
43 import org.apache.ibatis.annotations.Insert;
44 import org.apache.ibatis.annotations.InsertProvider;
45 import org.apache.ibatis.annotations.Lang;
46 import org.apache.ibatis.annotations.MapKey;
47 import org.apache.ibatis.annotations.Options;
48 import org.apache.ibatis.annotations.Result;
49 import org.apache.ibatis.annotations.ResultMap;
50 import org.apache.ibatis.annotations.ResultType;
51 import org.apache.ibatis.annotations.Results;
52 import org.apache.ibatis.annotations.Select;
53 import org.apache.ibatis.annotations.SelectKey;
54 import org.apache.ibatis.annotations.SelectProvider;
55 import org.apache.ibatis.annotations.TypeDiscriminator;
56 import org.apache.ibatis.annotations.Update;
57 import org.apache.ibatis.annotations.UpdateProvider;
58 import org.apache.ibatis.binding.BindingException;
59 import org.apache.ibatis.binding.MapperMethod.ParamMap;
60 import org.apache.ibatis.builder.BuilderException;
61 import org.apache.ibatis.builder.IncompleteElementException;
62 import org.apache.ibatis.builder.MapperBuilderAssistant;
63 import org.apache.ibatis.builder.xml.XMLMapperBuilder;
64 import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
65 import org.apache.ibatis.executor.keygen.KeyGenerator;
66 import org.apache.ibatis.executor.keygen.NoKeyGenerator;
67 import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
68 import org.apache.ibatis.io.Resources;
69 import org.apache.ibatis.mapping.Discriminator;
70 import org.apache.ibatis.mapping.FetchType;
71 import org.apache.ibatis.mapping.MappedStatement;
72 import org.apache.ibatis.mapping.ResultFlag;
73 import org.apache.ibatis.mapping.ResultMapping;
74 import org.apache.ibatis.mapping.ResultSetType;
75 import org.apache.ibatis.mapping.SqlCommandType;
76 import org.apache.ibatis.mapping.SqlSource;
77 import org.apache.ibatis.mapping.StatementType;
78 import org.apache.ibatis.scripting.LanguageDriver;
79 import org.apache.ibatis.session.Configuration;
80 import org.apache.ibatis.session.ResultHandler;
81 import org.apache.ibatis.session.RowBounds;
82 import org.apache.ibatis.type.JdbcType;
83 import org.apache.ibatis.type.TypeHandler;
84 import org.apache.ibatis.type.UnknownTypeHandler;
85
86
87
88
89 public class MapperAnnotationBuilder {
90
91 private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
92 private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();
93
94 private Configuration configuration;
95 private MapperBuilderAssistant assistant;
96 private Class<?> type;
97
98 public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
99 String resource = type.getName().replace('.', '/') + ".java (best guess)";
100 this.assistant = new MapperBuilderAssistant(configuration, resource);
101 this.configuration = configuration;
102 this.type = type;
103
104 sqlAnnotationTypes.add(Select.class);
105 sqlAnnotationTypes.add(Insert.class);
106 sqlAnnotationTypes.add(Update.class);
107 sqlAnnotationTypes.add(Delete.class);
108
109 sqlProviderAnnotationTypes.add(SelectProvider.class);
110 sqlProviderAnnotationTypes.add(InsertProvider.class);
111 sqlProviderAnnotationTypes.add(UpdateProvider.class);
112 sqlProviderAnnotationTypes.add(DeleteProvider.class);
113 }
114
115 public void parse() {
116 String resource = type.toString();
117 if (!configuration.isResourceLoaded(resource)) {
118 loadXmlResource();
119 configuration.addLoadedResource(resource);
120 assistant.setCurrentNamespace(type.getName());
121 parseCache();
122 parseCacheRef();
123 Method[] methods = type.getMethods();
124 for (Method method : methods) {
125 try {
126
127 if (!method.isBridge()) {
128 parseStatement(method);
129 }
130 } catch (IncompleteElementException e) {
131 configuration.addIncompleteMethod(new MethodResolver(this, method));
132 }
133 }
134 }
135 parsePendingMethods();
136 }
137
138 private void parsePendingMethods() {
139 Collection<MethodResolver> incompleteMethods = configuration.getIncompleteMethods();
140 synchronized (incompleteMethods) {
141 Iterator<MethodResolver> iter = incompleteMethods.iterator();
142 while (iter.hasNext()) {
143 try {
144 iter.next().resolve();
145 iter.remove();
146 } catch (IncompleteElementException e) {
147
148 }
149 }
150 }
151 }
152
153 private void loadXmlResource() {
154
155
156
157 if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
158 String xmlResource = type.getName().replace('.', '/') + ".xml";
159 InputStream inputStream = null;
160 try {
161 inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
162 } catch (IOException e) {
163
164 }
165 if (inputStream != null) {
166 XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
167 xmlParser.parse();
168 }
169 }
170 }
171
172 private void parseCache() {
173 CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
174 if (cacheDomain != null) {
175 Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
176 Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
177 assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), null);
178 }
179 }
180
181 private void parseCacheRef() {
182 CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
183 if (cacheDomainRef != null) {
184 assistant.useCacheRef(cacheDomainRef.value().getName());
185 }
186 }
187
188 private String parseResultMap(Method method) {
189 Class<?> returnType = getReturnType(method);
190 ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);
191 Results results = method.getAnnotation(Results.class);
192 TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
193 String resultMapId = generateResultMapName(method);
194 applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator);
195 return resultMapId;
196 }
197
198 private String generateResultMapName(Method method) {
199 StringBuilder suffix = new StringBuilder();
200 for (Class<?> c : method.getParameterTypes()) {
201 suffix.append("-");
202 suffix.append(c.getSimpleName());
203 }
204 if (suffix.length() < 1) {
205 suffix.append("-void");
206 }
207 return type.getName() + "." + method.getName() + suffix;
208 }
209
210 private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results, TypeDiscriminator discriminator) {
211 List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
212 applyConstructorArgs(args, returnType, resultMappings);
213 applyResults(results, returnType, resultMappings);
214 Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
215
216 assistant.addResultMap(resultMapId, returnType, null, disc, resultMappings, null);
217 createDiscriminatorResultMaps(resultMapId, returnType, discriminator);
218 }
219
220 private void createDiscriminatorResultMaps(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
221 if (discriminator != null) {
222 for (Case c : discriminator.cases()) {
223 String caseResultMapId = resultMapId + "-" + c.value();
224 List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
225
226 applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
227 applyResults(c.results(), resultType, resultMappings);
228
229 assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null);
230 }
231 }
232 }
233
234 private Discriminator applyDiscriminator(String resultMapId, Class<?> resultType, TypeDiscriminator discriminator) {
235 if (discriminator != null) {
236 String column = discriminator.column();
237 Class<?> javaType = discriminator.javaType() == void.class ? String.class : discriminator.javaType();
238 JdbcType jdbcType = discriminator.jdbcType() == JdbcType.UNDEFINED ? null : discriminator.jdbcType();
239 Class<? extends TypeHandler<?>> typeHandler = discriminator.typeHandler() == UnknownTypeHandler.class ? null : discriminator.typeHandler();
240 Case[] cases = discriminator.cases();
241 Map<String, String> discriminatorMap = new HashMap<String, String>();
242 for (Case c : cases) {
243 String value = c.value();
244 String caseResultMapId = resultMapId + "-" + value;
245 discriminatorMap.put(value, caseResultMapId);
246 }
247 return assistant.buildDiscriminator(resultType, column, javaType, jdbcType, typeHandler, discriminatorMap);
248 }
249 return null;
250 }
251
252 void parseStatement(Method method) {
253 Class<?> parameterTypeClass = getParameterType(method);
254 LanguageDriver languageDriver = getLanguageDriver(method);
255 SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
256 if (sqlSource != null) {
257 Options options = method.getAnnotation(Options.class);
258 final String mappedStatementId = type.getName() + "." + method.getName();
259 Integer fetchSize = null;
260 Integer timeout = null;
261 StatementType statementType = StatementType.PREPARED;
262 ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
263 SqlCommandType sqlCommandType = getSqlCommandType(method);
264 boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
265 boolean flushCache = !isSelect;
266 boolean useCache = isSelect;
267
268 KeyGenerator keyGenerator;
269 String keyProperty = "id";
270 String keyColumn = null;
271 if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
272
273 SelectKey selectKey = method.getAnnotation(SelectKey.class);
274 if (selectKey != null) {
275 keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
276 keyProperty = selectKey.keyProperty();
277 } else if (options == null) {
278 keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
279 } else {
280 keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
281 keyProperty = options.keyProperty();
282 keyColumn = options.keyColumn();
283 }
284 } else {
285 keyGenerator = new NoKeyGenerator();
286 }
287
288 if (options != null) {
289 flushCache = options.flushCache();
290 useCache = options.useCache();
291 fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null;
292 timeout = options.timeout() > -1 ? options.timeout() : null;
293 statementType = options.statementType();
294 resultSetType = options.resultSetType();
295 }
296
297 String resultMapId = null;
298 ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
299 if (resultMapAnnotation != null) {
300 String[] resultMaps = resultMapAnnotation.value();
301 StringBuilder sb = new StringBuilder();
302 for (String resultMap : resultMaps) {
303 if (sb.length() > 0) {
304 sb.append(",");
305 }
306 sb.append(resultMap);
307 }
308 resultMapId = sb.toString();
309 } else if (isSelect) {
310 resultMapId = parseResultMap(method);
311 }
312
313 assistant.addMappedStatement(
314 mappedStatementId,
315 sqlSource,
316 statementType,
317 sqlCommandType,
318 fetchSize,
319 timeout,
320
321 null,
322 parameterTypeClass,
323 resultMapId,
324 getReturnType(method),
325 resultSetType,
326 flushCache,
327 useCache,
328
329 false,
330 keyGenerator,
331 keyProperty,
332 keyColumn,
333
334 null,
335 languageDriver,
336
337 null);
338 }
339 }
340
341 private LanguageDriver getLanguageDriver(Method method) {
342 Lang lang = method.getAnnotation(Lang.class);
343 Class<?> langClass = null;
344 if (lang != null) {
345 langClass = lang.value();
346 }
347 return assistant.getLanguageDriver(langClass);
348 }
349
350 private Class<?> getParameterType(Method method) {
351 Class<?> parameterType = null;
352 Class<?>[] parameterTypes = method.getParameterTypes();
353 for (int i = 0; i < parameterTypes.length; i++) {
354 if (!RowBounds.class.isAssignableFrom(parameterTypes[i]) && !ResultHandler.class.isAssignableFrom(parameterTypes[i])) {
355 if (parameterType == null) {
356 parameterType = parameterTypes[i];
357 } else {
358
359 parameterType = ParamMap.class;
360 }
361 }
362 }
363 return parameterType;
364 }
365
366 private Class<?> getReturnType(Method method) {
367 Class<?> returnType = method.getReturnType();
368
369 if (void.class.equals(returnType)) {
370 ResultType rt = method.getAnnotation(ResultType.class);
371 if (rt != null) {
372 returnType = rt.value();
373 }
374 } else if (Collection.class.isAssignableFrom(returnType)) {
375 Type returnTypeParameter = method.getGenericReturnType();
376 if (returnTypeParameter instanceof ParameterizedType) {
377 Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
378 if (actualTypeArguments != null && actualTypeArguments.length == 1) {
379 returnTypeParameter = actualTypeArguments[0];
380 if (returnTypeParameter instanceof Class) {
381 returnType = (Class<?>) returnTypeParameter;
382 } else if (returnTypeParameter instanceof ParameterizedType) {
383
384 returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
385 } else if (returnTypeParameter instanceof GenericArrayType) {
386 Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
387
388 returnType = Array.newInstance(componentType, 0).getClass();
389 }
390 }
391 }
392 } else if (method.isAnnotationPresent(MapKey.class) && Map.class.isAssignableFrom(returnType)) {
393
394 Type returnTypeParameter = method.getGenericReturnType();
395 if (returnTypeParameter instanceof ParameterizedType) {
396 Type[] actualTypeArguments = ((ParameterizedType) returnTypeParameter).getActualTypeArguments();
397 if (actualTypeArguments != null && actualTypeArguments.length == 2) {
398 returnTypeParameter = actualTypeArguments[1];
399 if (returnTypeParameter instanceof Class) {
400 returnType = (Class<?>) returnTypeParameter;
401 } else if (returnTypeParameter instanceof ParameterizedType) {
402
403 returnType = (Class<?>) ((ParameterizedType) returnTypeParameter).getRawType();
404 }
405 }
406 }
407 }
408
409 return returnType;
410 }
411
412 private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) {
413 try {
414 Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
415 Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
416 if (sqlAnnotationType != null) {
417 if (sqlProviderAnnotationType != null) {
418 throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
419 }
420 Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
421 final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
422 return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
423 } else if (sqlProviderAnnotationType != null) {
424 Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
425 return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
426 }
427 return null;
428 } catch (Exception e) {
429 throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
430 }
431 }
432
433 private SqlSource buildSqlSourceFromStrings(String[] strings, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
434 final StringBuilder sql = new StringBuilder();
435 for (String fragment : strings) {
436 sql.append(fragment);
437 sql.append(" ");
438 }
439 return languageDriver.createSqlSource(configuration, sql.toString().trim(), parameterTypeClass);
440 }
441
442 private SqlCommandType getSqlCommandType(Method method) {
443 Class<? extends Annotation> type = getSqlAnnotationType(method);
444
445 if (type == null) {
446 type = getSqlProviderAnnotationType(method);
447
448 if (type == null) {
449 return SqlCommandType.UNKNOWN;
450 }
451
452 if (type == SelectProvider.class) {
453 type = Select.class;
454 } else if (type == InsertProvider.class) {
455 type = Insert.class;
456 } else if (type == UpdateProvider.class) {
457 type = Update.class;
458 } else if (type == DeleteProvider.class) {
459 type = Delete.class;
460 }
461 }
462
463 return SqlCommandType.valueOf(type.getSimpleName().toUpperCase(Locale.ENGLISH));
464 }
465
466 private Class<? extends Annotation> getSqlAnnotationType(Method method) {
467 return chooseAnnotationType(method, sqlAnnotationTypes);
468 }
469
470 private Class<? extends Annotation> getSqlProviderAnnotationType(Method method) {
471 return chooseAnnotationType(method, sqlProviderAnnotationTypes);
472 }
473
474 private Class<? extends Annotation> chooseAnnotationType(Method method, Set<Class<? extends Annotation>> types) {
475 for (Class<? extends Annotation> type : types) {
476 Annotation annotation = method.getAnnotation(type);
477 if (annotation != null) {
478 return type;
479 }
480 }
481 return null;
482 }
483
484 private void applyResults(Result[] results, Class<?> resultType, List<ResultMapping> resultMappings) {
485 for (Result result : results) {
486 List<ResultFlag> flags = new ArrayList<ResultFlag>();
487 if (result.id()) {
488 flags.add(ResultFlag.ID);
489 }
490 ResultMapping resultMapping = assistant.buildResultMapping(
491 resultType,
492 nullOrEmpty(result.property()),
493 nullOrEmpty(result.column()),
494 result.javaType() == void.class ? null : result.javaType(),
495 result.jdbcType() == JdbcType.UNDEFINED ? null : result.jdbcType(),
496 hasNestedSelect(result) ? nestedSelectId(result) : null,
497 null,
498 null,
499 null,
500 result.typeHandler() == UnknownTypeHandler.class ? null : result.typeHandler(),
501 flags,
502 null,
503 null,
504 isLazy(result));
505 resultMappings.add(resultMapping);
506 }
507 }
508
509 private String nestedSelectId(Result result) {
510 String nestedSelect = result.one().select();
511 if (nestedSelect.length() < 1) {
512 nestedSelect = result.many().select();
513 }
514 if (!nestedSelect.contains(".")) {
515 nestedSelect = type.getName() + "." + nestedSelect;
516 }
517 return nestedSelect;
518 }
519
520 private boolean isLazy(Result result) {
521 boolean isLazy = configuration.isLazyLoadingEnabled();
522 if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
523 isLazy = (result.one().fetchType() == FetchType.LAZY);
524 } else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
525 isLazy = (result.many().fetchType() == FetchType.LAZY);
526 }
527 return isLazy;
528 }
529
530 private boolean hasNestedSelect(Result result) {
531 if (result.one().select().length() > 0 && result.many().select().length() > 0) {
532 throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
533 }
534 return result.one().select().length() > 0 || result.many().select().length() > 0;
535 }
536
537 private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
538 for (Arg arg : args) {
539 List<ResultFlag> flags = new ArrayList<ResultFlag>();
540 flags.add(ResultFlag.CONSTRUCTOR);
541 if (arg.id()) {
542 flags.add(ResultFlag.ID);
543 }
544 ResultMapping resultMapping = assistant.buildResultMapping(
545 resultType,
546 null,
547 nullOrEmpty(arg.column()),
548 arg.javaType() == void.class ? null : arg.javaType(),
549 arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(),
550 nullOrEmpty(arg.select()),
551 nullOrEmpty(arg.resultMap()),
552 null,
553 null,
554 arg.typeHandler() == UnknownTypeHandler.class ? null : arg.typeHandler(),
555 flags,
556 null,
557 null,
558 false);
559 resultMappings.add(resultMapping);
560 }
561 }
562
563 private String nullOrEmpty(String value) {
564 return value == null || value.trim().length() == 0 ? null : value;
565 }
566
567 private Result[] resultsIf(Results results) {
568 return results == null ? new Result[0] : results.value();
569 }
570
571 private Arg[] argsIf(ConstructorArgs args) {
572 return args == null ? new Arg[0] : args.value();
573 }
574
575 private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId, Class<?> parameterTypeClass, LanguageDriver languageDriver) {
576 String id = baseStatementId + SelectKeyGenerator.SELECT_KEY_SUFFIX;
577 Class<?> resultTypeClass = selectKeyAnnotation.resultType();
578 StatementType statementType = selectKeyAnnotation.statementType();
579 String keyProperty = selectKeyAnnotation.keyProperty();
580 String keyColumn = selectKeyAnnotation.keyColumn();
581 boolean executeBefore = selectKeyAnnotation.before();
582
583
584 boolean useCache = false;
585 KeyGenerator keyGenerator = new NoKeyGenerator();
586 Integer fetchSize = null;
587 Integer timeout = null;
588 boolean flushCache = false;
589 String parameterMap = null;
590 String resultMap = null;
591 ResultSetType resultSetTypeEnum = null;
592
593 SqlSource sqlSource = buildSqlSourceFromStrings(selectKeyAnnotation.statement(), parameterTypeClass, languageDriver);
594 SqlCommandType sqlCommandType = SqlCommandType.SELECT;
595
596 assistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum,
597 flushCache, useCache, false,
598 keyGenerator, keyProperty, keyColumn, null, languageDriver, null);
599
600 id = assistant.applyCurrentNamespace(id, false);
601
602 MappedStatement keyStatement = configuration.getMappedStatement(id, false);
603 SelectKeyGenerator answer = new SelectKeyGenerator(keyStatement, executeBefore);
604 configuration.addKeyGenerator(id, answer);
605 return answer;
606 }
607
608 }