diff --git a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java index 54cd9a644d1..353ef7ee89a 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java @@ -465,7 +465,7 @@ private SqlSource getSqlSourceFromAnnotations(Method method, Class parameterT return buildSqlSourceFromStrings(strings, parameterType, languageDriver); } else if (sqlProviderAnnotationType != null) { Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); - return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation); + return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation, type, method); } return null; } catch (Exception e) { diff --git a/src/main/java/org/apache/ibatis/builder/annotation/ProviderContext.java b/src/main/java/org/apache/ibatis/builder/annotation/ProviderContext.java new file mode 100644 index 00000000000..38f0d46d427 --- /dev/null +++ b/src/main/java/org/apache/ibatis/builder/annotation/ProviderContext.java @@ -0,0 +1,60 @@ +/** + * Copyright 2009-2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.builder.annotation; + +import java.lang.reflect.Method; + +/** + * The context object for sql provider method. + * + * @author Kazuki Shimizu + * @since 3.4.5 + */ +public final class ProviderContext { + + private final Class mapperType; + private final Method mapperMethod; + + /** + * Constructor. + * + * @param mapperType A mapper interface type that specified provider + * @param mapperMethod A mapper method that specified provider + */ + ProviderContext(Class mapperType, Method mapperMethod) { + this.mapperType = mapperType; + this.mapperMethod = mapperMethod; + } + + /** + * Get a mapper interface type that specified provider. + * + * @return A mapper interface type that specified provider + */ + public Class getMapperType() { + return mapperType; + } + + /** + * Get a mapper method that specified provider. + * + * @return A mapper method that specified provider + */ + public Method getMapperMethod() { + return mapperMethod; + } + +} diff --git a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java index aa4132d99a6..438a6267621 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/ProviderSqlSource.java @@ -36,8 +36,22 @@ public class ProviderSqlSource implements SqlSource { private final Class providerType; private Method providerMethod; private String[] providerMethodArgumentNames; + private Class[] providerMethodParameterTypes; + private ProviderContext providerContext; + private Integer providerContextIndex; + /** + * @deprecated Please use the {@link #ProviderSqlSource(Configuration, Object, Class, Method)} instead of this. + */ + @Deprecated public ProviderSqlSource(Configuration config, Object provider) { + this(config, provider, null, null); + } + + /** + * @since 3.4.5 + */ + public ProviderSqlSource(Configuration config, Object provider, Class mapperType, Method mapperMethod) { String providerMethodName; try { this.sqlSourceParser = new SqlSourceBuilder(config); @@ -54,6 +68,7 @@ public ProviderSqlSource(Configuration config, Object provider) { } this.providerMethod = m; this.providerMethodArgumentNames = new ParamNameResolver(config, m).getNames(); + this.providerMethodParameterTypes = m.getParameterTypes(); } } } @@ -66,6 +81,18 @@ public ProviderSqlSource(Configuration config, Object provider) { throw new BuilderException("Error creating SqlSource for SqlProvider. Method '" + providerMethodName + "' not found in SqlProvider '" + this.providerType.getName() + "'."); } + for (int i = 0; i< this.providerMethodParameterTypes.length; i++) { + Class parameterType = this.providerMethodParameterTypes[i]; + if (parameterType == ProviderContext.class) { + if (this.providerContext != null){ + throw new BuilderException("Error creating SqlSource for SqlProvider. ProviderContext found multiple in SqlProvider method (" + + this.providerType.getName() + "." + providerMethod.getName() + + "). ProviderContext can not define multiple in SqlProvider method argument."); + } + this.providerContext = new ProviderContext(mapperType, mapperMethod); + this.providerContextIndex = i; + } + } } @Override @@ -77,12 +104,15 @@ public BoundSql getBoundSql(Object parameterObject) { private SqlSource createSqlSource(Object parameterObject) { try { Class[] parameterTypes = providerMethod.getParameterTypes(); + int bindParameterCount = parameterTypes.length - (providerContext == null ? 0 : 1); String sql; if (parameterTypes.length == 0) { sql = (String) providerMethod.invoke(providerType.newInstance()); - } else if (parameterTypes.length == 1 && - (parameterObject == null || parameterTypes[0].isAssignableFrom(parameterObject.getClass()))) { - sql = (String) providerMethod.invoke(providerType.newInstance(), parameterObject); + } else if (bindParameterCount == 0) { + sql = (String) providerMethod.invoke(providerType.newInstance(), providerContext); + } else if (bindParameterCount == 1 && + (parameterObject == null || parameterTypes[(providerContextIndex == null || providerContextIndex == 1) ? 0 : 1].isAssignableFrom(parameterObject.getClass()))) { + sql = (String) providerMethod.invoke(providerType.newInstance(), extractProviderMethodArguments(parameterObject)); } else if (parameterObject instanceof Map) { @SuppressWarnings("unchecked") Map params = (Map) parameterObject; @@ -91,7 +121,7 @@ private SqlSource createSqlSource(Object parameterObject) { throw new BuilderException("Error invoking SqlProvider method (" + providerType.getName() + "." + providerMethod.getName() + "). Cannot invoke a method that holds " - + (parameterTypes.length == 1 ? "named argument(@Param)": "multiple arguments") + + (bindParameterCount == 1 ? "named argument(@Param)": "multiple arguments") + " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object."); } Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); @@ -105,10 +135,25 @@ private SqlSource createSqlSource(Object parameterObject) { } } + private Object[] extractProviderMethodArguments(Object parameterObject) { + if (providerContext != null) { + Object[] args = new Object[2]; + args[providerContextIndex == 0 ? 1 : 0] = parameterObject; + args[providerContextIndex] = providerContext; + return args; + } else { + return new Object[] { parameterObject }; + } + } + private Object[] extractProviderMethodArguments(Map params, String[] argumentNames) { Object[] args = new Object[argumentNames.length]; for (int i = 0; i < args.length; i++) { - args[i] = params.get(argumentNames[i]); + if (providerContextIndex != null && providerContextIndex == i) { + args[i] = providerContext; + } else { + args[i] = params.get(argumentNames[i]); + } } return args; } diff --git a/src/site/es/xdoc/java-api.xml b/src/site/es/xdoc/java-api.xml index 3a6104eb3e0..3d00c479f03 100644 --- a/src/site/es/xdoc/java-api.xml +++ b/src/site/es/xdoc/java-api.xml @@ -1,7 +1,7 @@