package org.jdbi.v3.core.argument;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Stream;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import org.jdbi.v3.core.statement.StatementContext;
import org.jdbi.v3.core.statement.UnableToCreateStatementException;
import static java.util.stream.Collectors.toMap;
public class BeanPropertyArguments extends MethodReturnValueNamedArgumentFinder {
private static final Map<Class<?>, Map<String, PropertyDescriptor>> CLASS_PROPERTY_DESCRIPTORS = ExpiringMap
.builder()
.expiration(10, TimeUnit.MINUTES)
.expirationPolicy(ExpirationPolicy.ACCESSED)
.entryLoader((Class<?> type) -> {
try {
BeanInfo info = Introspector.getBeanInfo(type);
return Stream.of(info.getPropertyDescriptors())
.collect(toMap(PropertyDescriptor::getName, Function.identity()));
} catch (IntrospectionException e) {
throw new UnableToCreateStatementException(
"Failed to introspect object which is supposed to be used to "
+ "set named args for a statement via JavaBean properties", e);
}
})
.build();
private final Map<String, PropertyDescriptor> propertyDescriptors;
public BeanPropertyArguments(String prefix, Object bean) {
super(prefix, bean);
this.propertyDescriptors = CLASS_PROPERTY_DESCRIPTORS.get(bean.getClass());
}
@Override
Optional<TypedValue> getValue(String name, StatementContext ctx) {
PropertyDescriptor descriptor = propertyDescriptors.get(name);
if (descriptor == null) {
return Optional.empty();
}
Method getter = getGetter(name, descriptor, ctx);
Type type = getter.getGenericReturnType();
Object value = invokeMethod(getter, ctx);
return Optional.of(new TypedValue(type, value));
}
private Method getGetter(String name, PropertyDescriptor descriptor, StatementContext ctx) {
Method getter = descriptor.getReadMethod();
if (getter == null) {
throw new UnableToCreateStatementException(String.format("No getter method found for "
+ "bean property [%s] on [%s]",
name, object), ctx);
}
return getter;
}
@Override
NamedArgumentFinder getNestedArgumentFinder(Object obj) {
return new BeanPropertyArguments(null, obj);
}
@Override
public String toString() {
return "{lazy bean property arguments \"" + object + "\"";
}
}