/*
* 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 java.util.Arrays;
import java.util.List;
import org.jdbi.v3.core.statement.StatementContext;
import org.jdbi.v3.sqlobject.internal.ParameterUtil;
/**
* Defines a named attribute as a comma-separated {@link String} from the elements of the annotated array or
* {@link List} argument. Attributes are stored on the {@link StatementContext}, and may be used by
* statement customizers such as the statement rewriter. For example:
*
* <pre>
* @SqlUpdate("insert into <table> (<columns>) values (<values>)")
* int insert(@Define String table, @DefineList List<String> columns, @BindList List<Object> values);
*
* @SqlQuery("select <columns> from <table> where id = :id")
* ResultSet select(@DefineList("columns") List<String> columns, @Define("table") String table, @Bind("id") long id);
* </pre>
*
* <p>
* An array or {@code List} argument passed to {@code @DefineList} will be converted to a comma-separated String and set
* as a whole as a single specified attribute. Duplicate members in the {@code List} may cause SQL exceptions. An empty
* {@code List} or {@code null} members in the {@code List} will result in an {@link IllegalArgumentException}.
* </p>
*
* <p>
* Be aware of the list members you're binding with @DefineList, as there is no input sanitization! <b>Blindly passing
* Strings through <code>@DefineList</code> may make your application vulnerable to SQL Injection.</b>
* </p>
*
* @see Define
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@SqlStatementCustomizingAnnotation(DefineList.Factory.class)
public @interface DefineList
{
/**
* 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 key
*/
String value() default "";
final class Factory implements SqlStatementCustomizerFactory
{
@Override
public SqlStatementParameterCustomizer createForParameter(Annotation annotation,
Class<?> sqlObjectType,
Method method,
Parameter param,
int index,
Type type)
{
final DefineList d = (DefineList) annotation;
final String name = ParameterUtil.getParameterName(d, d.value(), param);
return (stmt, arg) -> {
List<?> argsList;
if (arg instanceof List) {
argsList = (List<?>) arg;
} else if (arg instanceof Object[]) {
argsList = Arrays.asList((Object[]) arg);
} else if (arg == null) {
throw new IllegalArgumentException("A null object was passed as a @DefineList parameter. " +
"@DefineList is only supported on List and array arguments");
} else {
throw new IllegalArgumentException("A " + arg.getClass() + " object was passed as a @DefineList " +
"parameter. @DefineList is only supported on List and array arguments");
}
if (argsList.isEmpty()) {
throw new IllegalArgumentException("An empty list was passed as a @DefineList parameter. Can't define " +
"an empty attribute.");
}
if (argsList.contains(null)) {
throw new IllegalArgumentException("A @DefineList parameter was passed a list with null values in it.");
}
stmt.defineList(name, argsList);
};
}
}
}