Skip to content

Commit

Permalink
fix(document): Fix FEEL document serialization (#3752)
Browse files Browse the repository at this point in the history
  • Loading branch information
sbuettner authored Dec 5, 2024
1 parent e25690e commit 692624e
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private <T> T mapJson(Class<T> cls) {
throw new ConnectorException("JSON_MISMATCH_ERROR", e.getOriginalMessage());
} catch (JsonProcessingException e) {
throw new ConnectorException(
"JSON_PROCESSING_ERROR", "Exception: " + e.getClass().getSimpleName() + "was raised");
"JSON_PROCESSING_ERROR", "Exception: " + e.getClass().getSimpleName() + " was raised", e);
}
}

Expand Down
10 changes: 9 additions & 1 deletion connector-sdk/feel-wrapper/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
<description>Common FEEL and Jackson configurations for Camunda Connectors</description>

<dependencies>
<dependency>
<groupId>io.camunda.connector</groupId>
<artifactId>jackson-datatype-document</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.camunda.feel</groupId>
<artifactId>feel-engine</artifactId>
Expand All @@ -29,7 +34,10 @@
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jdk8</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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. Camunda licenses this file to you under the Apache License,
* Version 2.0; you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.camunda.connector.feel;

import static io.camunda.connector.feel.JacksonSupport.MAP_TYPE_REFERENCE;

import com.fasterxml.jackson.databind.ObjectMapper;
import io.camunda.document.CamundaDocument;
import java.util.Optional;
import java.util.function.Function;
import org.camunda.feel.syntaxtree.Val;
import org.camunda.feel.valuemapper.JavaCustomValueMapper;

public class CustomValueMapper extends JavaCustomValueMapper {
private final ObjectMapper objectMapper;

public CustomValueMapper(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}

@Override
public Optional<Val> toValue(Object object, Function<Object, Val> innerValueMapper) {
// TODO: Refactor in scope of https://github.com/camunda/team-connectors/issues/964
if (object instanceof CamundaDocument document) {
// Make sure that documents are converted to references before accessing them in FEEL
// expressions
return Optional.of(
innerValueMapper.apply(
objectMapper.convertValue(document.reference(), MAP_TYPE_REFERENCE)));
} else return Optional.empty();
}

@Override
public Optional<Object> unpackValue(Val value, Function<Val, Object> innerValueMapper) {
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@
*/
package io.camunda.connector.feel;

import static io.camunda.connector.feel.JacksonSupport.MAP_TYPE_REFERENCE;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.scala.DefaultScalaModule$;
import io.camunda.connector.document.annotation.jackson.JacksonModuleDocument;
import io.camunda.document.factory.DocumentFactoryImpl;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -36,48 +40,37 @@
import org.camunda.feel.FeelEngine;
import org.camunda.feel.impl.JavaValueMapper;
import org.camunda.feel.impl.SpiServiceLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.collection.Iterable;
import scala.jdk.javaapi.CollectionConverters;

/** Wrapper for the FEEL engine, handling type conversions and expression evaluations. */
public class FeelEngineWrapper {

static final String ERROR_CONTEXT_IS_NULL = "Context is null";

static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<>() {};

private static final String ERROR_CONTEXT_IS_NULL = "Context is null";
private static final Logger log = LoggerFactory.getLogger(FeelEngineWrapper.class);
private final FeelEngine feelEngine;
private final ObjectMapper objectMapper;

/**
* Default constructor, creating an {@link ObjectMapper} and a {@link FeelEngine} with default
* configuration.
*/
public FeelEngineWrapper() {
this(
new FeelEngine.Builder()
.customValueMapper(new JavaValueMapper())
.functionProvider(SpiServiceLoader.loadFunctionProvider())
.build(),
this.objectMapper =
new ObjectMapper()
.registerModule(DefaultScalaModule$.MODULE$)
.registerModule(new JavaTimeModule())
// deserialize unknown types as empty objects
.registerModule(new Jdk8Module())
.registerModule(new JacksonModuleDocument(new DocumentFactoryImpl(null), null))
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS));
}
.disable(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS);

/**
* Injection constructor allowing to pass in the {@link FeelEngine} and {@link ObjectMapper} to
* use.
*
* @param feelEngine the FEEL engine to use
* @param objectMapper the object mapper to use
*/
public FeelEngineWrapper(final FeelEngine feelEngine, final ObjectMapper objectMapper) {
this.feelEngine = feelEngine;
this.objectMapper = objectMapper;
this.feelEngine =
new FeelEngine.Builder()
.customValueMapper(new JavaValueMapper())
.functionProvider(SpiServiceLoader.loadFunctionProvider())
.customValueMapper(new CustomValueMapper(objectMapper))
.build();
;
}

private static String trimExpression(final String expression) {
Expand Down Expand Up @@ -113,6 +106,7 @@ private Optional<Map<String, Object>> tryConvertToMap(Object o) {
try {
return Optional.of(objectMapper.convertValue(o, MAP_TYPE_REFERENCE));
} catch (IllegalArgumentException ex) {
log.warn(ex.getMessage(), ex);
return Optional.empty();
}
}
Expand Down Expand Up @@ -278,7 +272,6 @@ public String evaluateToJson(final String expression, final Object... variables)
private Object evaluateInternal(final String expression, final Object[] variables) {
var variablesAsMap = mergeMapVariables(variables);
var variablesAsMapAsScalaMap = toScalaMap(variablesAsMap);

var result = feelEngine.evalExpression(trimExpression(expression), variablesAsMapAsScalaMap);
if (result.isRight()) {
return result.right().get();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* 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. Camunda licenses this file to you under the Apache License,
* Version 2.0; you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.camunda.connector.feel;

import com.fasterxml.jackson.core.type.TypeReference;
import java.util.Map;

public class JacksonSupport {
protected static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE =
new TypeReference<>() {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public DocumentSerializer(DocumentOperationExecutor operationExecutor) {
public void serialize(
Document document, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException {

var reference = document.reference();
if (!(reference instanceof CamundaDocumentReference camundaReference)) {
throw new IllegalArgumentException("Unsupported document reference type: " + reference);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public static PropertyBuilder retryBackoff() {
return StringProperty.builder()
.id("retryBackoff")
.label("Retry backoff")
.feel(FeelMode.disabled)
.description("ISO-8601 duration to wait between retries")
.group("retries")
.value("PT0S");
Expand Down

0 comments on commit 692624e

Please sign in to comment.