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

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.

Related

use function to provide values for spring spel

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.

How to implement a translate function in ZK with EL

My webapp has a smart Java translator so I can't use a simple Label-based i18n in ZK. Instead of that, my smart translator method with two parameters - the key and the language - should be called, but the current language should be get from some webapp scope.
It would be very useful to have an EL function
<textbox value="${x:translate('some.i18n.key')}"/>
that call my smart translator method with the given String parameter and the current language code from the session.
What I could do is to define a 2-parameter translate method
<?xel-method prefix="x" name="translate"
class="mypackage.Translator"
signature="java.lang.Class translate(java.lang.String,java.lang.String)"?>
and use it as
<textbox value="${x:translate('qqq',sessionScope.get('LANGUAGE'))}"/>
but writing the second parameter every times is not a good solution. It can be written a bit shorter as
<textbox value="${x:translate('qqq',LANGUAGE)}"/>
or perhaps choose a shorter key instead of LANGUAGE, but I am interested in a more compact form.
Is it possible to define such a function that gets this second parameter implicitly? Or somehow to call a method of an object in the session/desktop/page scope from EL?
We use MVVM pattern with ZK and translation was one area where zk references make their way into your view models. To reduce it's impact we did:
Created a Translator interface:
public interface Translator {
String translate(String key);
// used where the string to be translation includes placeholders
// total_qty=Total qty: {1}
String translate(String key, String... params);
}
and a translator implementation:
public class ZKTranslator implements Translator {
#Override
public String translate(String s) {
String translation = Labels.getLabel(s);
if (translation == null)
return s;
return translation;
}
#Override
public String translate(String key, String... params) {
String translation = Labels.getLabel(key, params);
if (translation == null)
return key;
return translation;
}
}
This implementation may reference the session to retrieve a language or maybe (and preferable) the language is passed to ZKTranslator on initialisation.
It is then used as follows. In your view model:
public class SomeScreenVM {
private Translator translator;
#Init
public void init() {
// get language from session possibly
translator = new ZKTranslator(); // or new ZKTtanslator(language);
}
public String translate(String s) {
return translator.translate(s);
}
}
And in your zul file:
<window viewModel="#id('vm')" #init('com.example.SomeScreenVM')>
<label value="#load(vm.translate('hello'))"/>
</window>
Custom taglibs is the solution you need.
It exist in ZK :
https://www.zkoss.org/wiki/ZUML_Reference/ZUML/Processing_Instructions/taglib/Custom_Taglib
I can (miss)use the map support of EL in order to call a non-static, one-parameter method of an object.
First, I need a translator object implementing java.util.Map that can translate a String in its get() method:
public class Translator implements Map<String, String> {
private String language;
public Translator(String language) {
super();
this.language = language;
}
#Override
public String get(Object arg0) {
return translate(arg0.toString(), language);
}
...
}
Then I can put a translator into the desktop scope and use it in EL:
<zscript>
desktopScope.put("tr",new mypackage.Translator(sessionScope.get("language")));
</zscript>
<textbox value="${tr.some_key}"/>
<textbox value="${tr['some.hierarchical.key']}"/>
It is really not a clear solution, but makes the work with ZUL files rather simple.

How can you use different operators with QueryDSL Web?

I am using QueryDSL in my Spring Boot project and planning to use Spring's web support for it (current query dsl web docs). The problem is, I can't find anything about using different operators. How can I define a not equals or matches regex operation? At first glance, all it does is translating your ?fieldname=value format GET request to a predefined operation you set in your repository. Can I extend it in a way to allow multiple operations for the same field?
Example.:
Currently I can get a QueryDsl Predicate by passing URL paramters, like ?user.company.id=1:
#Controller
class UserController {
#Autowired UserRepository repository;
#RequestMapping(value = "/", method = RequestMethod.GET)
Page<User> getUsers(#QuerydslPredicate(root = User.class) Predicate predicate,
Pageable pageable) {
return repository.findAll(predicate, pageable);
}
}
But as the documentation I linked states, I can only define a single operation for a certain field. What If I want the Users, where the user.lastName starts with something and still keep the possibility to query for exact match? (?lastName=Xyz,contains and ?lastName=Xyz,equals maybe)
The QuerydslBinderCustomizer defines operations per field basis, but you can only define how to handle that particular field, there is no possibility to add multiple operations.
Maybe I cannot do this with QueryDSL, but then generally in Spring boot how do you apply filters to a search query?
I'm doing something like that. Although I'm facing some limitations when I try to do more complicated actions. What I've done in some steps:
Create a new interface MyBinderCustomizer<T extends EntityPath<?>> that extends QuerydslBinderCustomizer<QUser> (note the Q of User, you want QueryDSL autogenerated class instead of your entity).
Implement customize method. For example:
#Override
public default void customize(QuerydslBindings bindings, T root) {
bindings.bind(String.class).all(MyBinderCustomizer::applyStringComparison);
}
static BooleanExpression applyStringComparison(Path<String> path, Collection<? extends String> strings) {
BooleanExpression result = null;
for (String s : strings) {
try {
final String[] parts = s.split(",");
final String operator = parts[0];
final String value = parts.length > 1 ? parts[1] : null;
final Method method = Arrays.stream(path.getClass().getMethods())
.filter(m -> operator.equals(m.getName()))
.filter(m -> BooleanExpression.class.equals(m.getReturnType()))
.filter(m -> m.getParameterTypes().length == (value == null ? 0 : 1))
.filter(m -> value == null || m.getParameterTypes()[0].equals(String.class) || m.getParameterTypes()[0].equals(Object.class))
.findFirst().get();
final BooleanExpression be;
if (value == null) {
be = (BooleanExpression) method.invoke(path);
} else {
be = (BooleanExpression) method.invoke(path, value);
}
result = result == null ? be : result.and(be);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
return result;
}
Note you should change value/operator order, so you can call no-value operators like isNull.
Your repository must extend MyBinderCustomizer<QUser> (note Q again).
This will let you use these operations:
public BooleanExpression StringExpression.like(java.lang.String)
public BooleanExpression StringExpression.notLike(java.lang.String)
public BooleanExpression StringExpression.notEqualsIgnoreCase(java.lang.String)
public BooleanExpression StringExpression.containsIgnoreCase(java.lang.String)
public BooleanExpression StringExpression.likeIgnoreCase(java.lang.String)
public BooleanExpression StringExpression.startsWithIgnoreCase(java.lang.String)
public BooleanExpression StringExpression.endsWithIgnoreCase(java.lang.String)
public BooleanExpression StringExpression.equalsIgnoreCase(java.lang.String)
public BooleanExpression StringExpression.startsWith(java.lang.String)
public BooleanExpression StringExpression.endsWith(java.lang.String)
public BooleanExpression StringExpression.matches(java.lang.String)
public BooleanExpression StringExpression.contains(java.lang.String)
public BooleanExpression StringExpression.isEmpty()
public BooleanExpression StringExpression.isNotEmpty()
public BooleanExpression SimpleExpression.isNull()
public BooleanExpression SimpleExpression.isNotNull()
public BooleanExpression SimpleExpression.ne(java.lang.Object)
public BooleanExpression SimpleExpression.eq(java.lang.Object)
The Spring Data QueryDSL Value Operators library extends Spring Data QueryDSL web support with operators for not only String fields, but also Number and Enum fields. It requires some special configuration to make it work for the non-String fields, as explained here:
Value operators work seemlessly on String based properties/fields. However these operators do not work well with non-string values like Number or Enum since by default QuerydslPredicateArgumentResolver that resolves annotation QuerydslPredicate, which is used to annotate search handling method on RESTful method (aka RestController methods), performs strong-typing as per the guiding design principle of Querydsl, i.e. it attempts to convert the value(s) received from HTTP request to exact type defined in corresponding Q-Classes. This works well without value operators and is inline with Querydsl promise of allowing type-safe queries however hinders the path for value-operators to do their trick.
The library provides two methods to make operators work for non-String fields:
a Filter that extracts operators from query parameters, so the query parameters can still be converted to their corresponding type (using strong-typing)
replacing the ConversionService in the QuerydslPredicateArgumentResolver so all query parameters are treated as String (loosing the strong-typing)
Both approaches are well documented, along with their use case and disadvantages.
I am currently evaluating approach 1, as this fits our use case, but I need to extend it to accommodate DateTime fields and some custom operators as well.
https://bitbucket.org/gt_tech/spring-data-querydsl-value-operators/src/master/
Documentation here says:
QuerydslPredicateArgumentResolver uses ConversionService for type-conversion. Since conversion of String to Enum or String to Integer is core to Spring's dependency injection, it isn't advisable to change those default built-in converters (never do it). The library provides an experimental combination of a BeanPostProcessor and a ServletFilter that can be explicitly configured in target application's context to disable the strong type-conversion attempted by QuerydslPredicateArgumentResolver.
So to achieve this you need to add this to the application context:
/**
* Note the use of delegate ConversionService which comes handy for types like
* java.util.Date for handling powerful searches natively with Spring data.
* #param factory QuerydslBindingsFactory instance
* #param conversionServiceDelegate delegate ConversionService
* #return
*/
#Bean
public QuerydslPredicateArgumentResolverBeanPostProcessor querydslPredicateArgumentResolverBeanPostProcessor(
QuerydslBindingsFactory factory, DefaultFormattingConversionService conversionServiceDelegate) {
return new QuerydslPredicateArgumentResolverBeanPostProcessor(factory, conversionServiceDelegate);
}
Let me know if someone has success implementing this experimental functionality.
You can try using an additional lightweight library that helps to query fields using different operators LIKE, IN, EQ, NE etc. All you have to do is to add the dependency:
<dependency>
<groupId>io.github.apulbere</groupId>
<artifactId>rsql-querydsl</artifactId>
<version>1.0</version>
</dependency>
Define a model that will represent your search criteria:
#Setter
#Getter
public class UserCriteria {
StringCriteria lastName = StringCriteria.empty();
}
Use it as query parameter in your controller and build a dynamic predicate based on it:
#GetMapping("/users")
List<User> search(UserCriteria criteria, Pageable page) {
var predicate = criteria.lastName.match(QUser.user.lastName);
return userRepository.findAll(predicate, page);
}
Finally, make requests:
LIKE example: /users?lastName.like=Xyz
Equals examples: /users?lastName=Xyz or /users?lastName.eq=Xyz
There are other operators too.

Sort a list of objects based on a parameterized attribute of the object

Assuming that we have an object with the following attributes:
public class MyObject {
private String attr1;
private Integer attr2;
//...
public String getAttr1() {
return this.attr1;
}
public Integer getAttr2() {
return this.attr2;
}
}
One way of sorting a list mylist of this object, based on its attribute attr1 is:
mylist.sort(Comparator.comparing(MyObject::getAttr1));
Is it possible to use this code inside a method in a dynamic way and replace the getAttr1 part with a method that returns the getter of an attribute of the object based on its name? Something like:
public void sortListByAttr(List<MyObject> list, String attr) {
list.sort(Comparator.comparing(MyObject::getGetterByAttr(attr)));
}
The MyObject::getGetterByAttr(attr) part does not compile, I wrote it just as an example to explain my idea
I tried to implement a method with the following code new PropertyDescriptor(attr, MyObject.class).getReadMethod().invoke(new MyObject()) but It's still not possible to call a method with a parameter from the comparing method
You could add a method like
public static Function<MyObject,Object> getGetterByAttr(String s) {
switch(s) {
case "attr1": return MyObject::getAttr1;
case "attr2": return MyObject::getAttr2;
}
throw new IllegalArgumentException(s);
}
to your class, but the returned function is not suitable for Comparator.comparing, as it expects a type fulfilling U extends Comparable<? super U> and while each of String and Integer is capable of fulfilling this constraint in an individual invocation, there is no way to declare a generic return type for getGetterByAttr to allow both type and be still compatible with the declaration of comparing.
An alternative would be a factory for complete Comparators.
public static Comparator<MyObject> getComparator(String s) {
switch(s) {
case "attr1": return Comparator.comparing(MyObject::getAttr1);
case "attr2": return Comparator.comparing(MyObject::getAttr2);
}
throw new IllegalArgumentException(s);
}
to be used like
public void sortListByAttr(List<MyObject> list, String attr) {
list.sort(getComparator(attr));
}
This has the advantage that it also may support properties whose type is not Comparable and requires a custom Comparator. Also, more efficient comparators for primitive types (e.g. using comparingInt) would be possible.
You may also consider using a Map instead of switch:
private static Map<String,Comparator<MyObject>> COMPARATORS;
static {
Map<String,Comparator<MyObject>> comparators=new HashMap<>();
comparators.put("attr1", Comparator.comparing(MyObject::getAttr1));
comparators.put("attr2", Comparator.comparing(MyObject::getAttr2));
COMPARATORS = Collections.unmodifiableMap(comparators);
}
public static Comparator<MyObject> getComparator(String s) {
Comparator<MyObject> comparator = COMPARATORS.get(s);
if(comparator != null) return comparator;
throw new IllegalArgumentException(s);
}
More dynamic is only possible via Reflection, but this would complicate the code, add a lot of potential error source, with only little benefit, considering that you need only to add one line of source code for adding support for another property in either of the examples above. After all, the set of defined properties gets fixed at compile time.
You could also have a single place where this comparators would be defined:
static enum MyObjectComparator {
ATTR1("attr1", Comparator.comparing(MyObject::getAttr1));
MyObjectComparator(String attrName, Comparator<MyObject> comparator) {
this.comparator = comparator;
this.attrName = attrName;
}
private final Comparator<MyObject> comparator;
private final String attrName;
private static MyObjectComparator[] allValues = MyObjectComparator.values();
public static Comparator<MyObject> findByValue(String value) {
return Arrays.stream(allValues)
.filter(x -> x.attrName.equalsIgnoreCase(value))
.map(x -> x.comparator)
.findAny()
.orElseThrow(RuntimeException::new);
}
}
And your usage would be:
public void sortListByAttr(List<MyObject> list, String attr) {
list.sort(MyObjectComparator.findByValue(attr));
}

How to evaluate Spring EL in Spring EL

(Sorry if this is a duplicate, but the question is very search-engine-unfriendly.)
I want to know how to evaluate Spring EL inside EL (with all the functions, variables, context, etc. passed through).
Specifically, I want to dynamically evaluate a Spring Security expression (which is just EL plus some functions and contexts) loaded from a database entity inside a hard-coded EL in #PreAuthorize.
I.e. something like #PreAuthorize("eval(argument.securityExpr)").
You can extend Springs MethodSecurityExpressionRoot (and create it in your own MethodSecurityExpressionHandler) and add an eval method which excepts a String and let the SpringExpressionParser evaluate the String. Should work...
Edit:
A little code:
public class MySpringSecurityRoot extends MethodSecurityExpressionRoot {
private MyMethodSecurityExpressionHandler handler; // to be injected in the handler
public boolean eval(String expression) {
Expression expression = handler.getExpressionParser().parseExpression(expression);
return ExpressionUtils.evaluateAsBoolean(
handler.getExpressionParser().parseExpression(expression),
handler.createEvaluationContext(authentification, methodInvocation));
}
}
your handler must be set as the default method security expression handler:
<security:global-method-security pre-post-annotations="enabled">
<security:expression-handler ref="myHandler"/>
</security:global-method-security>
now your eval function is accessible in every method security expression
BUT: You must be aware of the fact that the person who describes your security rule, can access all beans inside the current spring context! Might be a security leak.
If you use a simpleparametername discover with # to evaluate permissions you can do virtually anything you want, without enabling debug mode.
#PreAuthorize("#mySecurityService.hasPermission(#arg0)")
public String getSpecial(final String special) {
return "authorized";
}
mySecurityService can be any bean/method returning a boolean, with this wired up for arg1
public class SimpleParameterNameDiscoverer implements ParameterNameDiscoverer {
public String[] getParameterNames(Method m) {
return getParameterNames(m.getParameterTypes().length);
}
public String[] getParameterNames(Constructor c) {
return getParameterNames(c.getParameterTypes().length);
}
protected String[] getParameterNames(int length) {
String[] names = new String[length];
for (int i = 0; i < length; i++)
names[i] = "arg" + i;
return names;
}
}
and context :
<bean id="methodSecurityExpressionHandler"
class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
<property name="parameterNameDiscoverer">
<bean class="your.path.SimpleParameterNameDiscoverer"/>
</property>

Resources