Skip to content

Commit 2c31be4

Browse files
committed
Upgrade JUEL to Jakarta Expression Language 6 and implement support for Lambda Expressions
* Add support for getting length of arrays * Add OptionalELResolver * Add RecordELResolver
1 parent 65c2712 commit 2c31be4

File tree

72 files changed

+4124
-2947
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

72 files changed

+4124
-2947
lines changed

modules/flowable-cdi/src/main/java/org/flowable/cdi/impl/el/CdiResolver.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
*/
1313
package org.flowable.cdi.impl.el;
1414

15-
import java.beans.FeatureDescriptor;
16-
import java.util.Iterator;
17-
1815
import jakarta.el.FunctionMapper;
1916
import jakarta.el.VariableMapper;
2017
import jakarta.enterprise.inject.spi.BeanManager;
@@ -81,11 +78,6 @@ public Class<?> getCommonPropertyType(ELContext context, Object base) {
8178
return getWrappedResolver().getCommonPropertyType(this.context, base);
8279
}
8380

84-
@Override
85-
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context, Object base) {
86-
return null;
87-
}
88-
8981
@Override
9082
public Class<?> getType(ELContext context, Object base, Object property) {
9183
return getWrappedResolver().getType(this.context, base, property);

modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/ExpressionFactoryImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ private Class<?> load(Class<?> clazz, Properties properties) {
434434
}
435435

436436
@Override
437-
public final Object coerceToType(Object obj, Class<?> targetType) {
437+
public final <T> T coerceToType(Object obj, Class<T> targetType) {
438438
return converter.convert(obj, targetType);
439439
}
440440

modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/TreeMethodExpression.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ public boolean isLiteralText() {
144144
* @return <code>true</code> if this is a method invocation expression
145145
*/
146146
@Override
147-
public boolean isParmetersProvided() {
147+
public boolean isParametersProvided() {
148148
return node.isMethodInvocation();
149149
}
150150

modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/misc/TypeConverterImpl.java

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@
1717

1818
import java.beans.PropertyEditor;
1919
import java.beans.PropertyEditorManager;
20+
import java.lang.reflect.Method;
21+
import java.lang.reflect.Modifier;
22+
import java.lang.reflect.Proxy;
2023
import java.math.BigDecimal;
2124
import java.math.BigInteger;
25+
import java.util.function.Supplier;
2226

2327
import org.flowable.common.engine.impl.javax.el.ELException;
28+
import org.flowable.common.engine.impl.javax.el.LambdaExpression;
2429

2530
/**
2631
* Type Conversions as described in EL 2.1 specification (section 1.17).
@@ -303,12 +308,48 @@ protected Object coerceStringToType(String value, Class<?> type) {
303308
}
304309
}
305310

311+
protected <T> T coerceToFunctionalInterface(LambdaExpression lambdaExpression, Class<T> type) {
312+
Supplier<T> proxy = () -> {
313+
// Create a dynamic proxy for the functional interface
314+
@SuppressWarnings("unchecked")
315+
T result = (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type },
316+
(Object obj, Method method, Object[] args) -> {
317+
// Functional interfaces have a single, abstract method
318+
if (!Modifier.isAbstract(method.getModifiers())) {
319+
throw new ELException(LocalMessages.get("error.coerce.nonAbstract", type, method));
320+
}
321+
return lambdaExpression.invoke(args);
322+
});
323+
return result;
324+
};
325+
return proxy.get();
326+
}
327+
306328
@SuppressWarnings("unchecked")
307329
protected Object coerceToType(Object value, Class<?> type) {
308-
if (type == null || Object.class.equals(type)) {
330+
if (type == null) {
309331
return value;
310332
}
311333

334+
if (value instanceof LambdaExpression lambdaExpression) {
335+
if (LambdaExpression.class == type) {
336+
return lambdaExpression;
337+
}
338+
if (isFunctionalInterface(type)) {
339+
return coerceToFunctionalInterface(lambdaExpression, (Class<?>) type);
340+
}
341+
342+
if (lambdaExpression.getFormalParameters().isEmpty()) {
343+
// If the value is a LambdaExpression without formal parameters we need to resolve its value
344+
value = coerceToType(lambdaExpression.invoke(), type);
345+
}
346+
}
347+
348+
if (Object.class.equals(type)) {
349+
return value;
350+
}
351+
352+
312353
if (type == String.class) {
313354
return coerceToString(value);
314355
}
@@ -372,4 +413,53 @@ public int hashCode() {
372413
public <T> T convert(Object value, Class<T> type) throws ELException {
373414
return (T)coerceToType(value, type);
374415
}
416+
417+
// Copied from Tomcat org.apache.el.lang.ELSupport
418+
static boolean isFunctionalInterface(Class<?> type) {
419+
420+
if (!type.isInterface()) {
421+
return false;
422+
}
423+
424+
boolean foundAbstractMethod = false;
425+
Method[] methods = type.getMethods();
426+
for (Method method : methods) {
427+
if (Modifier.isAbstract(method.getModifiers())) {
428+
// Abstract methods that override one of the public methods
429+
// of Object don't count
430+
if (overridesObjectMethod(method)) {
431+
continue;
432+
}
433+
if (foundAbstractMethod) {
434+
// Found more than one
435+
return false;
436+
} else {
437+
foundAbstractMethod = true;
438+
}
439+
}
440+
}
441+
return foundAbstractMethod;
442+
}
443+
444+
// Copied from Tomcat org.apache.el.lang.ELSupport
445+
private static boolean overridesObjectMethod(Method method) {
446+
// There are three methods that can be overridden
447+
if ("equals".equals(method.getName())) {
448+
if (method.getReturnType().equals(boolean.class)) {
449+
if (method.getParameterCount() == 1) {
450+
return method.getParameterTypes()[0].equals(Object.class);
451+
}
452+
}
453+
} else if ("hashCode".equals(method.getName())) {
454+
if (method.getReturnType().equals(int.class)) {
455+
return method.getParameterCount() == 0;
456+
}
457+
} else if ("toString".equals(method.getName())) {
458+
if (method.getReturnType().equals(String.class)) {
459+
return method.getParameterCount() == 0;
460+
}
461+
}
462+
463+
return false;
464+
}
375465
}

0 commit comments

Comments
 (0)