Skip to content

Commit 3c1378c

Browse files
committed
Add JUEL tests and improve implementation
Copy tests for the Odysseus JUEL implementation from https://github.com/beckchr/juel Copy Tomcat EL implementation tests from https://github.com/apache/tomcat/tree/97f157af6d750a644a3d1c26e2d6af0d59d7b553/test/org/apache/el Implement some improvements for JUEL: * Support BigInteger literal - prior to this if a number larger than a long was used it would throw an exception during expression parsing * Support BigDecimal literal - prior to this if a number larger than a double would be used we would have an an infinite double, now it will use BigDecimal * Support using other quote escaping in string literal according to: https://jakarta.ee/specifications/expression-language/6.0/jakarta-expression-language-spec-6.0#literals. * `\` must be escaped as `\\` * `"` must be escaped as `\"` when the string is enclosed with `"` * `"` may be escaped as `\"` when the string is enclosed with `'` * `'` must be escaped as `\'` when the string is enclosed with `'` * `'` may be escaped as `\'` when the string is enclosed with `"` * Adjust Odysseus TypeConverter to coerce null to non primitive types according to the JUEL spec (https://jakarta.ee/specifications/expression-language/6.0/jakarta-expression-language-spec-6.0#type-conversion). This leads to the changes in `AstBinary`, `AstChoice` and `AstUnary` to use a primitive boolean class to do the binding. It is safe to do since the other place where we coerce values is the BeanELResolver, in which we only call the coercion with a null value for primitives
1 parent 1882460 commit 3c1378c

File tree

86 files changed

+7856
-13
lines changed

Some content is hidden

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

86 files changed

+7856
-13
lines changed

modules/flowable-engine-common/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@
120120

121121
<dependency>
122122
<groupId>org.junit.jupiter</groupId>
123-
<artifactId>junit-jupiter-engine</artifactId>
123+
<artifactId>junit-jupiter</artifactId>
124124
<scope>test</scope>
125125
</dependency>
126126
<dependency>

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,16 @@ protected Object coerceStringToType(String value, Class<?> type) {
305305

306306
@SuppressWarnings("unchecked")
307307
protected Object coerceToType(Object value, Class<?> type) {
308+
if (type == null || Object.class.equals(type)) {
309+
return value;
310+
}
311+
308312
if (type == String.class) {
309313
return coerceToString(value);
310314
}
315+
if (value == null && !type.isPrimitive()) {
316+
return null;
317+
}
311318
if (type == Long.class || type == long.class) {
312319
return coerceToLong(value);
313320
}

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

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import static org.flowable.common.engine.impl.de.odysseus.el.tree.impl.Scanner.Symbol.TEXT;
4040
import static org.flowable.common.engine.impl.de.odysseus.el.tree.impl.Scanner.Symbol.TRUE;
4141

42+
import java.math.BigDecimal;
43+
import java.math.BigInteger;
4244
import java.util.ArrayList;
4345
import java.util.Collections;
4446
import java.util.HashMap;
@@ -191,8 +193,16 @@ protected ExtensionHandler getExtensionHandler(Token token) {
191193
*/
192194
protected Number parseInteger(String string) throws ParseException {
193195
try {
194-
return Long.valueOf(string);
195-
} catch (NumberFormatException e) {
196+
try {
197+
return Long.valueOf(string);
198+
} catch (NumberFormatException e) {
199+
// Too large for Long. Try BigInteger.
200+
return new BigInteger(string);
201+
}
202+
} catch (ArithmeticException | NumberFormatException exception) {
203+
// Too big for BigInteger.
204+
// Catch NumberFormatException as well here just in case the
205+
// parser provides invalid input.
196206
fail(INTEGER);
197207
return null;
198208
}
@@ -205,7 +215,11 @@ protected Number parseInteger(String string) throws ParseException {
205215
*/
206216
protected Number parseFloat(String string) throws ParseException {
207217
try {
208-
return Double.valueOf(string);
218+
Double value = Double.valueOf(string);
219+
if (value.isInfinite() || value.isNaN()) {
220+
return new BigDecimal(string);
221+
}
222+
return value;
209223
} catch (NumberFormatException e) {
210224
fail(FLOAT);
211225
return null;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,10 @@ protected Token nextString() throws ScanException {
309309
throw new ScanException(position, "unterminated string", quote + " or \\");
310310
} else {
311311
c = input.charAt(i++);
312-
if (c == '\\' || c == quote) {
312+
if (c == '\\' || c == '\'' || c == '\"') {
313313
builder.append(c);
314314
} else {
315-
throw new ScanException(position, "invalid escape sequence \\" + c, "\\" + quote + " or \\\\");
315+
throw new ScanException(position, "invalid escape sequence \\" + c, "\\', \\\" or \\\\");
316316
}
317317
}
318318
} else if (c == quote) {

modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/tree/impl/ast/AstBinary.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public Object eval(Bindings bindings, ELContext context, AstNode left, AstNode r
4040
public static final Operator AND = new Operator() {
4141
@Override
4242
public Object eval(Bindings bindings, ELContext context, AstNode left, AstNode right) {
43-
Boolean l = bindings.convert(left.eval(bindings, context), Boolean.class);
44-
return Boolean.TRUE.equals(l) ? bindings.convert(right.eval(bindings, context), Boolean.class) : Boolean.FALSE;
43+
Boolean l = bindings.convert(left.eval(bindings, context), boolean.class);
44+
return Boolean.TRUE.equals(l) ? bindings.convert(right.eval(bindings, context), boolean.class) : Boolean.FALSE;
4545
}
4646
@Override public String toString() { return "&&"; }
4747
};
@@ -84,8 +84,8 @@ public Object eval(Bindings bindings, ELContext context, AstNode left, AstNode r
8484
public static final Operator OR = new Operator() {
8585
@Override
8686
public Object eval(Bindings bindings, ELContext context, AstNode left, AstNode right) {
87-
Boolean l = bindings.convert(left.eval(bindings, context), Boolean.class);
88-
return Boolean.TRUE.equals(l) ? Boolean.TRUE : bindings.convert(right.eval(bindings, context), Boolean.class);
87+
Boolean l = bindings.convert(left.eval(bindings, context), boolean.class);
88+
return Boolean.TRUE.equals(l) ? Boolean.TRUE : bindings.convert(right.eval(bindings, context), boolean.class);
8989
}
9090
@Override public String toString() { return "||"; }
9191
};

modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/tree/impl/ast/AstChoice.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ public AstChoice(AstNode question, AstNode yes, AstNode no) {
3030

3131
@Override
3232
public Object eval(Bindings bindings, ELContext context) throws ELException {
33-
Boolean value = bindings.convert(question.eval(bindings, context), Boolean.class);
34-
return value.booleanValue() ? yes.eval(bindings, context) : no.eval(bindings, context);
33+
boolean value = bindings.convert(question.eval(bindings, context), boolean.class);
34+
return value ? yes.eval(bindings, context) : no.eval(bindings, context);
3535
}
3636

3737
@Override

modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/de/odysseus/el/tree/impl/ast/AstUnary.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public Object eval(Bindings bindings, ELContext context, AstNode node) {
4343
@Override public String toString() { return "-"; }
4444
};
4545
public static final Operator NOT = new SimpleOperator() {
46-
@Override public Object apply(TypeConverter converter, Object o) { return !converter.convert(o, Boolean.class); }
46+
@Override public Object apply(TypeConverter converter, Object o) { return !converter.convert(o, boolean.class); }
4747
@Override public String toString() { return "!"; }
4848
};
4949

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2006-2009 Odysseus Software GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.flowable.common.engine.impl.de.odysseus.el;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import org.flowable.common.engine.impl.de.odysseus.el.util.SimpleContext;
21+
import org.flowable.common.engine.impl.de.odysseus.el.util.SimpleResolver;
22+
import org.junit.jupiter.api.Test;
23+
24+
class ExpressionFactoryImplTest {
25+
26+
public static long bar() {
27+
return 1;
28+
}
29+
30+
private ExpressionFactoryImpl factory = new ExpressionFactoryImpl();
31+
32+
@Test
33+
void testCoerceToType() {
34+
assertThat(factory.coerceToType(1l, String.class)).isEqualTo("1");
35+
}
36+
37+
@Test
38+
void testCreateTreeValueExpression() {
39+
SimpleContext context = new SimpleContext(new SimpleResolver());
40+
assertThat(factory.createValueExpression(context, "${1}", Object.class).getValue(context)).isEqualTo(1l);
41+
}
42+
43+
@Test
44+
void testCreateObjectValueExpression() {
45+
SimpleContext context = new SimpleContext(new SimpleResolver());
46+
assertThat(factory.createValueExpression("1", Object.class).getValue(context)).isEqualTo("1");
47+
}
48+
49+
@Test
50+
void testCreateMethodExpression() throws NoSuchMethodException {
51+
SimpleContext context = new SimpleContext(new SimpleResolver());
52+
context.getELResolver().setValue(context, null, "foo", this);
53+
assertThat(factory.createMethodExpression(context, "${foo.bar}", null, new Class[0]).invoke(context, null)).isEqualTo(bar());
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2006-2009 Odysseus Software GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.flowable.common.engine.impl.de.odysseus.el;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
20+
21+
import org.flowable.common.engine.impl.de.odysseus.el.misc.TypeConverter;
22+
import org.flowable.common.engine.impl.javax.el.ELException;
23+
import org.junit.jupiter.api.Test;
24+
25+
class ObjectValueExpressionTest extends TestCase {
26+
27+
private TypeConverter converter = TypeConverter.DEFAULT;
28+
29+
@Test
30+
void testHashCode() {
31+
assertThat(new ObjectValueExpression(converter, "foo", Object.class).hashCode()).isEqualTo("foo".hashCode());
32+
}
33+
34+
@Test
35+
void testEqualsObject() {
36+
assertThat(new ObjectValueExpression(converter, "foo", Object.class).equals(new ObjectValueExpression(converter, "foo", Object.class))).isTrue();
37+
assertThat(new ObjectValueExpression(converter, new String("foo"), Object.class).equals(
38+
new ObjectValueExpression(converter, "foo", Object.class))).isTrue();
39+
assertThat(new ObjectValueExpression(converter, "foo", Object.class).equals(new ObjectValueExpression(converter, "bar", Object.class))).isFalse();
40+
}
41+
42+
@Test
43+
void testGetValue() {
44+
assertThat(new ObjectValueExpression(converter, "foo", Object.class).getValue(null)).isEqualTo("foo");
45+
}
46+
47+
@Test
48+
void testGetExpressionString() {
49+
assertThat(new ObjectValueExpression(converter, "foo", Object.class).getExpressionString()).isNull();
50+
}
51+
52+
@Test
53+
void testIsLiteralText() {
54+
assertThat(new ObjectValueExpression(converter, "foo", Object.class).isLiteralText()).isFalse();
55+
}
56+
57+
@Test
58+
void testGetType() {
59+
assertThat(new ObjectValueExpression(converter, "foo", Object.class).getType(null)).isNull();
60+
}
61+
62+
@Test
63+
void testIsReadOnly() {
64+
assertThat(new ObjectValueExpression(converter, "foo", Object.class).isReadOnly(null)).isTrue();
65+
}
66+
67+
@Test
68+
void testSetValue() {
69+
assertThatThrownBy(() -> new ObjectValueExpression(converter, "foo", Object.class).setValue(null, "bar"))
70+
.isInstanceOf(ELException.class);
71+
}
72+
73+
@Test
74+
void testSerialize() throws Exception {
75+
ObjectValueExpression expression = new ObjectValueExpression(converter, "foo", Object.class);
76+
assertThat(deserialize(serialize(expression))).isEqualTo(expression);
77+
}
78+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2006-2009 Odysseus Software GmbH
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.flowable.common.engine.impl.de.odysseus.el;
17+
18+
import java.io.ByteArrayInputStream;
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.ObjectInput;
21+
import java.io.ObjectInputStream;
22+
import java.io.ObjectOutput;
23+
import java.io.ObjectOutputStream;
24+
25+
import org.flowable.common.engine.impl.de.odysseus.el.tree.Tree;
26+
import org.flowable.common.engine.impl.de.odysseus.el.tree.TreeStore;
27+
import org.flowable.common.engine.impl.de.odysseus.el.tree.impl.Builder;
28+
import org.flowable.common.engine.impl.de.odysseus.el.util.SimpleContext;
29+
import org.flowable.common.engine.impl.javax.el.ELContext;
30+
import org.flowable.common.engine.impl.javax.el.ExpressionFactory;
31+
import org.flowable.common.engine.impl.javax.el.ValueExpression;
32+
33+
public abstract class TestCase {
34+
35+
protected static final Builder BUILDER = new Builder(Builder.Feature.METHOD_INVOCATIONS);
36+
37+
protected ExpressionFactory expressionFactory;
38+
39+
protected static Tree parse(String expression) {
40+
return BUILDER.build(expression);
41+
}
42+
43+
protected Object eval(String expression) {
44+
return eval(expression, Object.class);
45+
}
46+
protected Object eval(String expression, ELContext context) {
47+
return eval(expression, context, Object.class);
48+
}
49+
50+
protected <T> T eval(String expression, Class<T> expectedType) {
51+
return eval(expression, null, expectedType);
52+
}
53+
protected <T> T eval(String expression, ELContext context, Class<T> expectedType) {
54+
if (context == null) {
55+
context = new SimpleContext();
56+
}
57+
ValueExpression valueExpression = getExpressionFactory().createValueExpression(context, expression, expectedType);
58+
return (T) valueExpression.getValue(context);
59+
}
60+
61+
protected ExpressionFactory getExpressionFactory() {
62+
if (expressionFactory == null) {
63+
expressionFactory = createExpressionFactory();
64+
}
65+
return expressionFactory;
66+
}
67+
68+
protected ExpressionFactory createExpressionFactory() {
69+
TreeStore store = new TreeStore(BUILDER, null);
70+
return new ExpressionFactoryImpl(store);
71+
}
72+
73+
protected static byte[] serialize(Object value) throws Exception {
74+
ByteArrayOutputStream bout = new ByteArrayOutputStream();
75+
ObjectOutput out = new ObjectOutputStream(bout);
76+
out.writeObject(value);
77+
out.close();
78+
return bout.toByteArray();
79+
}
80+
81+
protected static Object deserialize(byte[] bytes) throws Exception {
82+
ByteArrayInputStream bin = new ByteArrayInputStream(bytes);
83+
ObjectInput in = new ObjectInputStream(bin);
84+
return in.readObject();
85+
}
86+
}

0 commit comments

Comments
 (0)