Skip to content

Commit

Permalink
Merge #5392
Browse files Browse the repository at this point in the history
5392: Dump feel-scala from 1.11.2 to 1.12.2 r=saig0 a=saig0

## Description

* dump `feel-scala` from `1.11.2` to `1.12.2`
* implement the FEEL clock API using the ActorClock
* update docs about FEEL expressions
 
## Related issues

closes #5140 
closes #4212 

## Definition of Done

_Not all items need to be done depending on the issue and the pull request._

Code changes:
* [x] The changes are backwards compatibility with previous versions
* [ ] If it fixes a bug then PRs are created to [backport](https://github.com/zeebe-io/zeebe/compare/stable/0.24...develop?expand=1&template=backport_template.md&title=[Backport%200.24]) the fix to the last two minor versions

Testing:
* [x] There are unit/integration tests that verify all acceptance criterias of the issue
* [ ] New tests are written to ensure backwards compatibility with further versions
* [x] The behavior is tested manually
* [ ] The impact of the changes is verified by a benchmark 

Documentation: 
* [x] The documentation is updated (e.g. BPMN reference, configuration, examples, get-started guides, etc.)
* [x] New content is added to the [release announcement](https://drive.google.com/drive/u/0/folders/1DTIeswnEEq-NggJ25rm2BsDjcCQpDape)


Co-authored-by: Philipp Ossler <philipp.ossler@gmail.com>
  • Loading branch information
zeebe-bors[bot] and saig0 committed Sep 24, 2020
2 parents 7fa20a7 + cd5c866 commit cd1ebb2
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 20 deletions.
46 changes: 36 additions & 10 deletions docs/src/reference/expressions.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ An expression is written in **FEEL** (Friendly Enough Expression Language). FEEL
* Simple syntax designed for business professionals and developers
* Three-valued logic (true, false, null)

Zeebe integrates the [Feel-Scala](https://github.com/camunda/feel-scala) engine (version `1.11.x`) to evaluate FEEL expressions. The following sections cover common use cases in Zeebe. A complete list of supported expressions can be found in the project's [documentation](https://camunda.github.io/feel-scala/1.11/).
Zeebe integrates the [Feel-Scala](https://github.com/camunda/feel-scala) engine (version `1.12.x`) to evaluate FEEL expressions. The following sections cover common use cases in Zeebe. A complete list of supported expressions can be found in the project's [documentation](https://camunda.github.io/feel-scala/1.12/).

### Access Variables

Expand Down Expand Up @@ -123,14 +123,27 @@ orderCount >= 5 and orderCount < 15
orderCount > 15 or totalPrice > 50
```

### Null Checks

If a variable or a nested property can be `null` then it can be compared to the `null` value. Comparing `null` to a value different from `null` results in `false`.

```feel
order = null
// true if order is null
// true - if "order" is null or doesn't exist
order.id = null
// true - if "order" is null, "order" doesn't exist,
// "id" is null, or "order" has no property "id"
```

In addition to the comparison with `null`, the built-in function `is defined()` can be used to differentiate between a value that is `null` and a value that doesn’t exist.

```feel
is defined(order)
// true - if "order" has any value or is null
totalCount > 5
// false is totalCount is null
is defined(order.id)
// false - if "order" doesn't exist or it has no property "id"
```

### String Expressions
Expand All @@ -149,7 +162,7 @@ Any value can be transformed into a string value using the `string()` function.
// "order-123"
```

More functions for string values are available as [built-in functions](https://camunda.github.io/feel-scala/1.11/feel-built-in-functions#string-functions) (e.g. contains, matches, etc.).
More functions for string values are available as [built-in functions](https://camunda.github.io/feel-scala/1.12/feel-built-in-functions#string-functions) (e.g. contains, matches, etc.).

### Temporal Expressions

Expand Down Expand Up @@ -238,6 +251,19 @@ cycle(duration("P7D"))
// "R/P7D"
```

The current date and date-time can be accessed using the built-in functions `today()` and `now()`. In order to store the current date or date-time in a variable, it must be converted to a string using the built-in function `string()`.

```feel
now()
// date and time("2020-04-06T15:30:00@UTC")
today()
// date("2020-04-06")
string(today())
// "2020-04-06"
```

### List Expressions

An element of a list can be accessed by its index. The index starts at `1` with the first element (**not** at `0`). A negative index starts at the end by `-1`. If the index is out of the range of the list then `null` is returned instead.
Expand Down Expand Up @@ -272,7 +298,7 @@ some x in [1,2,3] satisfies x > 2

### Invoke Functions

FEEL defines a set of [built-in functions](https://camunda.github.io/feel-scala/1.11/feel-built-in-functions) to convert values and to apply different operations on specific value types in addition to the operators.
FEEL defines a set of [built-in functions](https://camunda.github.io/feel-scala/1.12/feel-built-in-functions) to convert values and to apply different operations on specific value types in addition to the operators.

A function can be invoked by its name followed by the arguments. The arguments can be assigned to the function parameters either by their position or by defining the parameter names.

Expand All @@ -293,8 +319,8 @@ contains(string: "foobar", match: "foo")
## Additional Resources

References:
* [FEEL-Scala - Documentation](https://camunda.github.io/feel-scala/1.11/)
* [FEEL - Data Types](https://camunda.github.io/feel-scala/1.11/feel-data-types)
* [FEEL - Expressions](https://camunda.github.io/feel-scala/1.11/feel-expression)
* [FEEL - Built-in Functions](https://camunda.github.io/feel-scala/1.11/feel-built-in-functions)
* [FEEL-Scala - Documentation](https://camunda.github.io/feel-scala/1.12/)
* [FEEL - Data Types](https://camunda.github.io/feel-scala/1.12/feel-data-types)
* [FEEL - Expressions](https://camunda.github.io/feel-scala/1.12/feel-expression)
* [FEEL - Built-in Functions](https://camunda.github.io/feel-scala/1.12/feel-built-in-functions)
* [DMN Specification](https://www.omg.org/spec/DMN/About-DMN/)
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
package io.zeebe.el;

import io.zeebe.el.impl.FeelExpressionLanguage;
import io.zeebe.util.sched.clock.ActorClock;

/** The entry point to create the default {@link ExpressionLanguage}. */
public class ExpressionLanguageFactory {

/** @return a new instance of the {@link ExpressionLanguage} */
public static ExpressionLanguage createExpressionLanguage() {
return new FeelExpressionLanguage();
return new FeelExpressionLanguage(ActorClock.current());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import io.zeebe.el.impl.feel.FeelToMessagePackTransformer;
import io.zeebe.el.impl.feel.FeelVariableContext;
import io.zeebe.el.impl.feel.MessagePackValueMapper;
import io.zeebe.util.sched.clock.ActorClock;
import java.util.regex.Pattern;
import org.camunda.feel.FeelEngine;
import org.camunda.feel.FeelEngine.Failure;
Expand All @@ -36,15 +37,20 @@ public final class FeelExpressionLanguage implements ExpressionLanguage {

private static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\=(.+)", Pattern.DOTALL);

private final FeelEngine feelEngine =
new FeelEngine.Builder()
.customValueMapper(new MessagePackValueMapper())
.functionProvider(new FeelFunctionProvider())
.build();

private final FeelToMessagePackTransformer messagePackTransformer =
new FeelToMessagePackTransformer();

private final FeelEngine feelEngine;

public FeelExpressionLanguage(final ActorClock clock) {
feelEngine =
new FeelEngine.Builder()
.customValueMapper(new MessagePackValueMapper())
.functionProvider(new FeelFunctionProvider())
.clock(new ZeebeFeelEngineClock(clock))
.build();
}

@Override
public Expression parseExpression(final String expression) {
ensureNotNull("expression", expression);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
* one or more contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright ownership.
* Licensed under the Zeebe Community License 1.0. You may not use this file
* except in compliance with the Zeebe Community License 1.0.
*/
package io.zeebe.el.impl;

import io.zeebe.util.sched.clock.ActorClock;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.camunda.feel.FeelEngineClock;

public final class ZeebeFeelEngineClock implements FeelEngineClock {

private final ActorClock clock;

public ZeebeFeelEngineClock(final ActorClock clock) {
this.clock = clock;
}

@Override
public ZonedDateTime getCurrentTime() {

final long currentMillis = clock.getTimeMillis();
final var instant = Instant.ofEpochMilli(currentMillis);
final var zone = ZoneId.systemDefault();

return instant.atZone(zone);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
import static io.zeebe.test.util.MsgPackUtil.asMsgPack;
import static org.assertj.core.api.Assertions.assertThat;

import io.zeebe.el.impl.FeelExpressionLanguage;
import io.zeebe.util.sched.clock.ControlledActorClock;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Map;
import org.junit.Test;
Expand All @@ -18,8 +22,9 @@ public class FeelExpressionTest {

private static final EvaluationContext EMPTY_CONTEXT = name -> null;

private final ExpressionLanguage expressionLanguage =
ExpressionLanguageFactory.createExpressionLanguage();
private final ControlledActorClock clock = new ControlledActorClock();

private final ExpressionLanguage expressionLanguage = new FeelExpressionLanguage(clock);

@Test
public void stringLiteral() {
Expand Down Expand Up @@ -157,6 +162,72 @@ public void listProjection() {
assertThat(evaluationResult.getList()).isEqualTo(List.of(asMsgPack("1"), asMsgPack("2")));
}

@Test
public void getCurrentTime() {
final var localDateTime = LocalDateTime.parse("2020-09-21T07:20:00");
final var now = localDateTime.atZone(ZoneId.systemDefault());
clock.setCurrentTime(now.toInstant());

final var evaluationResult = evaluateExpression("now()", EMPTY_CONTEXT);

assertThat(evaluationResult.getType()).isEqualTo(ResultType.DATE_TIME);
assertThat(evaluationResult.getDateTime()).isEqualTo(now);
}

@Test
public void getCurrentDate() {
final var localDateTime = LocalDateTime.parse("2020-09-21T07:20:00");
final var now = localDateTime.atZone(ZoneId.systemDefault());
clock.setCurrentTime(now.toInstant());

final var evaluationResult = evaluateExpression("string(today())", EMPTY_CONTEXT);

assertThat(evaluationResult.getType()).isEqualTo(ResultType.STRING);
assertThat(evaluationResult.getString()).isEqualTo(now.toLocalDate().toString());
}

@Test
public void nullCheckWithNonExistingVariable() {
final var evaluationResult = evaluateExpression("x = null", EMPTY_CONTEXT);

assertThat(evaluationResult.getType()).isEqualTo(ResultType.BOOLEAN);
assertThat(evaluationResult.getBoolean()).isTrue();
}

@Test
public void nullCheckWithNestedNonExistingVariable() {
final var evaluationResult = evaluateExpression("x.y = null", EMPTY_CONTEXT);

assertThat(evaluationResult.getType()).isEqualTo(ResultType.BOOLEAN);
assertThat(evaluationResult.getBoolean()).isTrue();
}

@Test
public void checkIfDefinedWithNonExistingVariable() {
final var evaluationResult = evaluateExpression("is defined(x)", EMPTY_CONTEXT);

assertThat(evaluationResult.getType()).isEqualTo(ResultType.BOOLEAN);
assertThat(evaluationResult.getBoolean()).isFalse();
}

@Test
public void checkIfDefinedWithNestedNonExistingVariable() {
final var evaluationResult = evaluateExpression("is defined(x.y)", EMPTY_CONTEXT);

assertThat(evaluationResult.getType()).isEqualTo(ResultType.BOOLEAN);
assertThat(evaluationResult.getBoolean()).isFalse();
}

@Test
public void checkIfDefinedWithNullVariable() {
final var context = Map.of("x", asMsgPack("null"));

final var evaluationResult = evaluateExpression("is defined(x)", context::get);

assertThat(evaluationResult.getType()).isEqualTo(ResultType.BOOLEAN);
assertThat(evaluationResult.getBoolean()).isTrue();
}

private EvaluationResult evaluateExpression(
final String expression, final EvaluationContext context) {
final var parseExpression = expressionLanguage.parseExpression("=" + expression);
Expand Down
2 changes: 1 addition & 1 deletion parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
<version.testcontainers>1.14.3</version.testcontainers>
<version.netflix.concurrency>0.3.6</version.netflix.concurrency>
<version.zeebe-test-container>1.0.0</version.zeebe-test-container>
<version.feel-scala>1.11.2</version.feel-scala>
<version.feel-scala>1.12.2</version.feel-scala>
<version.restassert>4.3.1</version.restassert>
<version.spring-framework>5.2.9.RELEASE</version.spring-framework>
<version.spring-boot>2.3.4.RELEASE</version.spring-boot>
Expand Down

0 comments on commit cd1ebb2

Please sign in to comment.