diff --git a/connector-runtime/connector-runtime-core/src/main/java/io/camunda/connector/runtime/core/outbound/JobHandlerContext.java b/connector-runtime/connector-runtime-core/src/main/java/io/camunda/connector/runtime/core/outbound/JobHandlerContext.java index 5d1c4948dd..b3349428db 100644 --- a/connector-runtime/connector-runtime-core/src/main/java/io/camunda/connector/runtime/core/outbound/JobHandlerContext.java +++ b/connector-runtime/connector-runtime-core/src/main/java/io/camunda/connector/runtime/core/outbound/JobHandlerContext.java @@ -122,7 +122,7 @@ private T mapJson(Class 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); } } diff --git a/connector-sdk/feel-wrapper/pom.xml b/connector-sdk/feel-wrapper/pom.xml index 6ef9d46d3c..13bb88ea4c 100644 --- a/connector-sdk/feel-wrapper/pom.xml +++ b/connector-sdk/feel-wrapper/pom.xml @@ -13,6 +13,11 @@ Common FEEL and Jackson configurations for Camunda Connectors + + io.camunda.connector + jackson-datatype-document + ${project.version} + org.camunda.feel feel-engine @@ -29,7 +34,10 @@ com.fasterxml.jackson.datatype jackson-datatype-jsr310 - + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + org.junit.jupiter junit-jupiter diff --git a/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/CustomValueMapper.java b/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/CustomValueMapper.java new file mode 100644 index 0000000000..36fec03d79 --- /dev/null +++ b/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/CustomValueMapper.java @@ -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 toValue(Object object, Function 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 unpackValue(Val value, Function innerValueMapper) { + return Optional.empty(); + } +} diff --git a/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/FeelEngineWrapper.java b/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/FeelEngineWrapper.java index 06b4e08947..401cd7a2f3 100644 --- a/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/FeelEngineWrapper.java +++ b/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/FeelEngineWrapper.java @@ -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; @@ -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_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) { @@ -113,6 +106,7 @@ private Optional> tryConvertToMap(Object o) { try { return Optional.of(objectMapper.convertValue(o, MAP_TYPE_REFERENCE)); } catch (IllegalArgumentException ex) { + log.warn(ex.getMessage(), ex); return Optional.empty(); } } @@ -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(); diff --git a/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/JacksonSupport.java b/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/JacksonSupport.java new file mode 100644 index 0000000000..fd7d91a3fd --- /dev/null +++ b/connector-sdk/feel-wrapper/src/main/java/io/camunda/connector/feel/JacksonSupport.java @@ -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_TYPE_REFERENCE = + new TypeReference<>() {}; +} diff --git a/connector-sdk/jackson-datatype-document/src/main/java/io/camunda/connector/document/annotation/jackson/serializer/DocumentSerializer.java b/connector-sdk/jackson-datatype-document/src/main/java/io/camunda/connector/document/annotation/jackson/serializer/DocumentSerializer.java index 356a77cf08..7a47026303 100644 --- a/connector-sdk/jackson-datatype-document/src/main/java/io/camunda/connector/document/annotation/jackson/serializer/DocumentSerializer.java +++ b/connector-sdk/jackson-datatype-document/src/main/java/io/camunda/connector/document/annotation/jackson/serializer/DocumentSerializer.java @@ -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); diff --git a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/CommonProperties.java b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/CommonProperties.java index c696865477..c20cd27264 100644 --- a/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/CommonProperties.java +++ b/element-template-generator/core/src/main/java/io/camunda/connector/generator/dsl/CommonProperties.java @@ -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");