use function to provide values for spring spel - spring

I am using spring evaluation language as follow:
Map<String, Object> bigMap = loader.loadBigMap();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariables(bigMap);
my issue is that most of the data contained in the bigMap is not used.
Instead I would prefer to lazy load only what I need by passing a function to the evaluation context, something like:
Function<String, Object> lazyloader = name -> loader.loadForName(name);
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariables(lazyloader);
but I could not find how to do this. Any suggestion ?

You can't do it with a lambda - SpEL functions must be static methods.
public void registerFunction(String name, Method method)
public class MyUtils {
public static Object lazyLoader(String key) {
...
}
}
Use Class.getDeclaredMethod(...) to get a reference to the method.

Related

FormUrlEncoded POST request, I need to convert snake case values into camelCase with SpringBoot and Jackson

I am integrating with a third-party's vendor API.
I have a SpringBoot and Jackson setup
They are sending me a POST request that is of type formUrlEncoded and with the params in snake_case
(over 10 params in total and no body)
e.g.
POST www.example.com?player_id=somePlayerId&product_id=someProductId&total_amount=totalAmount...
There are many out of the box helpers for JSON but I cannot find any for formUrlEncoded (I hope I am missing something obvious).
I have tried #ModelAttribute and #RequestParam but had no luck.
I am trying to avoid the #RequestParam MultiValueMap<String, String> params + custom mapper option
#RequestParam is the simplest way which allows you to define the exact name of the query parameter something like:
#PostMapping
public String foo(#RequestParam("player_id") String playerId){
}
If you want to bind all the query parameters to an object , you have to use #ModelAttribute. It is based on the DataBinder and is nothing to do with Jackson. By default it only supports binding the query parameter to an object which fields have the same name as the query parameter. So you can consider to bind the query paramater to the following object :
public class Request {
private String player_id;
private String product_id;
private Long total_amount;
}
If you really want to bind to the object that follow traditional java naming convention (i.e lower camel case) from the query parameter that has snake case values , you have to cusomtize WebDataBinder.
The idea is to override its addBindValues() and check if the query parameter name is in snake case format , convert it the lower camel case format and also add it as the bind values for the request. Something like :
public class MyServletRequestDataBinder extends ExtendedServletRequestDataBinder {
private static Converter<String, String> snakeCaseToLowerCamelConverter = CaseFormat.LOWER_UNDERSCORE
.converterTo(CaseFormat.LOWER_CAMEL);
public MyServletRequestDataBinder(Object target) {
super(target);
}
public MyServletRequestDataBinder(Object target, String objectName) {
super(target, objectName);
}
#Override
protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
super.addBindValues(mpvs, request);
Enumeration<String> paramNames = request.getParameterNames();
while (paramNames != null && paramNames.hasMoreElements()) {
String paramName = paramNames.nextElement();
if(paramName.contains("_")) {
String[] values = request.getParameterValues(paramName);
if (values == null || values.length == 0) {
// Do nothing, no values found at all.
} else if (values.length > 1) {
mpvs.addPropertyValue(snakeCaseToLowerCamelConverter.convert(paramName), values);
} else {
mpvs.addPropertyValue(snakeCaseToLowerCamelConverter.convert(paramName), values[0]);
}
}
}
}
}
P.S I am using Guava for helping me to convert snake case to lowerCamelCase.
But in order to use the customized WebDataBinder , you have to in turn customize WebDataBinderFactory and RequestMappingHandlerAdapter because :
customize WebDataBinderFactory in order to create the customised WebDataBinder
customize RequestMappingHandlerAdapter in order to create the WebDataBinderFactory
Something like:
public class MyServletRequestDataBinderFactory extends ServletRequestDataBinderFactory {
public MyServletRequestDataBinderFactory(List<InvocableHandlerMethod> binderMethods,
WebBindingInitializer initializer) {
super(binderMethods, initializer);
}
#Override
protected ServletRequestDataBinder createBinderInstance(Object target, String objectName,
NativeWebRequest request) throws Exception {
return new MyServletRequestDataBinder(target, objectName);
}
}
and
public class MyRequestMappingHandlerAdapter extends RequestMappingHandlerAdapter {
#Override
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
throws Exception {
return new MyServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}
}
And finally register to use the customised RequestMappingHandlerAdapter in your configuration :
#Configuration
public class Config extends DelegatingWebMvcConfiguration {
#Override
protected RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
return new MyRequestMappingHandlerAdapter();
}
}
I don't think you are missing anything. Looking at the RequestParamMethodArgumentResolver#resolveName source I do no see a way to customize how a request parameter is matched. So it looks either you have to implement your own resolver or just annotate each parameter with #RequestParam and provide the name, e.g. #RequestParam("product_id") String productId
EDIT:
As for ModelAttribute, ModelAttributeMethodProcessor uses WebDataBinder. Again you can customize it with your custom DataBinder but I didn't found any that out of the box supports aliases as Jackson does.

SpEL parsing parameters with "Collectors.toList()" in stream won't work as expressionString?

This will work
parser.parseExpression("#configList.stream().toArray()").getValue(context)
but the following won't
parser.parseExpression("#configList.stream().map(o -> o.ruleId).collect(Collectors.toList())").getValue(context)
F.Y.I the context is constructed as follows:
Object[] args = joinPoint.getArgs();
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
String[] params = discoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < params.length; i++) {
context.setVariable(params[i], args[i]);
}
Although Java can be used in SPeL expressions, SPeL itself is a separate language and does not fully support the Java language. From the documentation:
SpEL is based on a technology agnostic API allowing other expression language implementations to be integrated should the need arise.
To perform filtering and mapping operations on a List in a SPeL expression, use collection selection and collection projection respectively:
Collection selection example
// Java:
configList.stream().filter(o -> o.getRuleId() > 2).collect(Collectors.toList())
// SPeL (notice the question mark) :
"#configList.?[ruleId>2]"
Collection projection example
// Java:
configList.stream().map(o -> o.getRuleId()).collect(Collectors.toList())
// SPeL (notice the exclamation mark) :
"#configList.![ruleId]"
I have set up a small example to demonstrate it:
public class So64738543ExpressionTest {
public static class RuleItem {
private int ruleId;
public RuleItem(int ruleId) {
this.ruleId = ruleId;
}
public int getRuleId() {
return ruleId;
}
}
#Test
public void collectionProjection() {
List<RuleItem> ruleItems = Arrays.asList(new RuleItem(1), new RuleItem(2), new RuleItem(3));
EvaluationContext context = new StandardEvaluationContext(ruleItems);
Expression expression = new SpelExpressionParser().parseExpression("#root.![ruleId]");
Assert.assertEquals(Arrays.asList(1,2,3), expression.getValue(context));
}
}
[edit]
Furthermore, if a SPeL expression becomes increasingly complex, I highly recommend to move the expression to a static method and invoke it using a T operator. Don't forget to include the fully qualified package name when referring to the static method.

Proper use of Spring's ResolvableType to determine generic type

I'm attempting to use Spring's org.springframework.core.ResolvableType to figure out the parameterized type at runtime as such:
public class MyClass<T> implements MyInterface<T> {
private final Class<T> typeClass;
public CustomStateSerializer() {
ResolvableType type = ResolvableType.forClass(getClass());
ResolvableType genericType = type.getGeneric();
this.typeClass= (Class<T>) genericType.resolve();
}
}
...
new MyClass<MyType>();
Unfortunately, genericType results to ?. Clearly I'm not using it correctly and I can't seem to find any good docs for the solution.
The class is only has reference to which is not defined yet. So you can pass the instance that will have an specific already:
ResolvableType type = ResolvableType.forClass(getClass(), this);
this.typeClass = type.getGeneric(0).resolve();

Spring's #Condtional annotation with Java 8

I was using #Conditional annotation and had to provide implementation of matches() of Condition interface. Since the Condition is a FunctionalInterface, how can I use Java 8 lambda to provide implementation in annotation rather than providing implementation.
#Conditional(value = MyCondition.class)
class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return ifMatches;
}
}
You can't.
Lambdas are instances of classes and #Conditional needs the class itself.
The lambda
Function<String, String> capitalize = text -> text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase();
Is functionally identical to
Function<String, String> capitalize = new Function<String, String>() {
public String apply(String text) {
return Function<String, String> capitalize = text -> text.substring(0, 1).toUpperCase() + text.substring(1).toLowerCase();
}
};
Both are instances of anonymous classes and not the anonymous class itself. There is no way to actually access anonymous classes (outside of reflection, but you can't use reflection in annotations).
Passing a a lambda where a class is expected would be like passing a String where a class is expected. I.e. something like this:
public #interface MyAnnotation {
Class<?> value();
}
#MyAnnotation("this is a string")
public class MyClass {
}
which you wouldn't expect to work.

How to cache an instrumented class with an instance forwarder?

The use case is to implement a dirty field tracker. For this I have an interface:
public interface Dirtyable {
String ID = "dirty";
Set<String> getDirty();
static <T> T wrap(final T delegate) {
return DirtyableInterceptor.wrap(delegate, ReflectionUtils::getPropertyName);
}
static <T> T wrap(final T delegate, final Function<Method, String> resolver) {
return DirtyableInterceptor.wrap(delegate, resolver);
}
}
In the interceptor class the wrapping method is:
static <T> T wrap(final T delegate, final Function<Method, String> resolver) {
requireNonNull(delegate, "Delegate must be non-null");
requireNonNull(resolver, "Resolver must be non-null");
final Try<Class<T>> delegateClassTry = Try.of(() -> getClassForType(delegate.getClass()));
return delegateClassTry.flatMapTry(delegateClass ->
dirtyableFor(delegate, delegateClass, resolver))
.mapTry(Class::newInstance)
.getOrElseThrow(t -> new IllegalStateException(
"Could not wrap dirtyable for " + delegate.getClass(), t));
}
The method dirtyableFor defines a ByteBuddy which forwards to a specific instance at each call. However, instrumenting at every invocation is a bit expensive so it caches the instrumented subclass from the given instance's class. For this I use the resilience4j library (a.k.a. javaslang-circuitbreaker).
private static <T> Try<Class<? extends T>> dirtyableFor(final T delegate,
final Class<T> clazz,
final Function<Method, String> resolver) {
long start = System.currentTimeMillis();
Try<Class<? extends T>> r = Try.of(() -> ofCheckedSupplier(() ->
new ByteBuddy().subclass(clazz)
.defineField(Dirtyable.ID, Set.class, Visibility.PRIVATE)
.method(nameMatches("getDirty"))
.intercept(reference(new HashSet<>()))
.implement(Dirtyable.class)
.method(not(isDeclaredBy(Object.class))
.and(not(isAbstract()))
.and(isPublic()))
.intercept(withDefaultConfiguration()
.withBinders(Pipe.Binder.install(Function.class))
.to(new DirtyableInterceptor(delegate, resolver)))
.make().load(clazz.getClassLoader())
.getLoaded())
.withCache(getCache())
.decorate()
.apply(clazz));
System.out.println("Instrumentation time: " + (System.currentTimeMillis() - start));
return r;
}
private static <T> Cache<Class<? super T>, Class<T>> getCache() {
final CachingProvider provider = Caching.getCachingProvider();
final CacheManager manager = provider.getCacheManager();
final javax.cache.Cache<Class<? super T>, Class<T>> cache =
manager.getCache(Dirtyable.ID);
final Cache<Class<? super T>, Class<T>> dirtyCache = Cache.of(cache);
dirtyCache.getEventStream().map(Object::toString).subscribe(logger::debug);
return dirtyCache;
}
From the logs, the intrumentation time drops from 70-100ms for a cache miss to 0-2ms for a cache hit.
For completeness here is the interceptor method:
#RuntimeType
#SuppressWarnings("unused")
public Object intercept(final #Origin Method method, final #This Dirtyable dirtyable,
final #Pipe Function<Object, Object> pipe) throws Throwable {
if (ReflectionUtils.isSetter(method)) {
final String property = resolver.apply(method);
dirtyable.getDirty().add(property);
logger.debug("Intercepted setter [{}], resolved property " +
"[{}] flagged as dirty.", method, property);
}
return pipe.apply(this.delegate);
}
This solution works well, except that the DirtyableInterceptor is always the same for cache hits, so the delegate instance is also the same.
Is it possible to bind a forwarder to a supplier of an instance so that intercepted methods would forward to it? How could this be done?
You can create a stateless interceptor by making your intercept method static. To access the object's state, define two fields on your subclass which you access using the #FieldValue annotations in your now static interceptor. Instead of using the FixedValue::reference instrumentation, you would also need to use the FieldAccessor implementation to read the value. You also need to define the fields using the defineField builder method.
You can set these fields either by:
Adding setter methods in your Dirtyable interface and intercepting them using the FieldAccessor implementation.
Defining an explicit constructor to which you supply the values. This also allows you to define the fields to be final. To implement the constructor, you first need to invoke a super constructor and then call the FieldAccessor several times to set the fields.
Doing so, you have created a fully stateless class that you can reuse but one that you need to initialze. Byte Buddy already offers a built-in TypeCache for easy reuse.

Resources