/*
* 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.jdbi.v3.sqlobject.customizer;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import org.jdbi.v3.core.internal.IterableLike;
import org.jdbi.v3.sqlobject.internal.ParameterUtil;
/**
* Binds each value in the annotated {@link Iterable} or array/varargs argument, and defines a named attribute as a
* comma-separated list of each bound parameter name. Common use cases:
* <p>
* <pre>
* @SqlQuery("select * from things where id in (<ids>)")
* List<Thing> getThings(@BindList int... ids)
*
* @SqlQuery("insert into things (<columnNames>) values (<values>)")
* void insertThings(@DefineList List<String> columnNames, @BindList List<Object> values)
* </pre>
* <p>
* <p>
* Throws IllegalArgumentException if the argument is not an array or Iterable. How null and empty collections are handled can be configured with onEmpty:EmptyHandling - throws IllegalArgumentException by default.
* </p>
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
@SqlStatementCustomizingAnnotation(BindList.Factory.class)
public @interface BindList {
/**
* The attribute name to define. If omitted, the name of the annotated parameter is used. It is an error to omit
* the name when there is no parameter naming information in your class files.
*
* @return the attribute name.
*/
String value() default "";
/**
* what to do when the argument is null or empty
*/
EmptyHandling onEmpty() default EmptyHandling.THROW;
final class Factory implements SqlStatementCustomizerFactory {
@Override
public SqlStatementParameterCustomizer createForParameter(Annotation annotation,
Class<?> sqlObjectType,
Method method,
Parameter param,
int index,
Type type) {
final BindList bindList = (BindList) annotation;
final String name = ParameterUtil.getParameterName(bindList, bindList.value(), param);
return (stmt, arg) -> {
if (arg == null || IterableLike.isEmpty(arg)) {
switch (bindList.onEmpty()) {
case VOID:
stmt.define(name, "");
return;
case NULL:
stmt.define(name, "null");
return;
case THROW:
throw new IllegalArgumentException(arg == null
? "argument is null; null was explicitly forbidden on this instance of BindList"
: "argument is empty; emptiness was explicitly forbidden on this instance of BindList");
default:
throw new IllegalStateException(EmptyHandling.valueNotHandledMessage);
}
}
stmt.bindList(name, IterableLike.toList(arg));
};
}
}
/**
* describes what needs to be done if the passed argument is null or empty
*/
enum EmptyHandling {
/**
* output "" (without quotes, i.e. nothing)
* <p>
* select * from things where x in ()
*/
VOID,
/**
* output "null" (without quotes, as keyword), useful e.g. in postgresql where "in ()" is invalid syntax
* <p>
* select * from things where x in (null)
*/
NULL,
/**
* throw IllegalArgumentException
*/
THROW;
static final String valueNotHandledMessage = "EmptyHandling type on BindList not handled. Please report this to the jdbi developers.";
}
}