Skip to content

Commit

Permalink
fix(inbound): add FEEL expression parser for properties in inbound co…
Browse files Browse the repository at this point in the history
…nnectors (#470)
  • Loading branch information
Oleksiivanov authored and chillleader committed Jul 27, 2023
1 parent a166b64 commit 1661850
Show file tree
Hide file tree
Showing 3 changed files with 420 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* 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.runtime.util.feel;

import fastparse.Parsed;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import org.camunda.feel.impl.parser.FeelParser;
import org.camunda.feel.syntaxtree.ArithmeticNegation;
import org.camunda.feel.syntaxtree.ConstBool;
import org.camunda.feel.syntaxtree.ConstContext;
import org.camunda.feel.syntaxtree.ConstDate;
import org.camunda.feel.syntaxtree.ConstDateTime;
import org.camunda.feel.syntaxtree.ConstList;
import org.camunda.feel.syntaxtree.ConstLocalDateTime;
import org.camunda.feel.syntaxtree.ConstNull;
import org.camunda.feel.syntaxtree.ConstNumber;
import org.camunda.feel.syntaxtree.ConstString;
import org.camunda.feel.syntaxtree.Exp;
import scala.Tuple2;
import scala.collection.immutable.List;
import scala.math.BigDecimal;

public class FeelParserWrapper {

@SuppressWarnings("unchecked")
public static Object parseIfIsFeelExpressionOrGetOriginal(final Object value) {
if (value instanceof Map) {
return ((Map<Object, Object>) value)
.entrySet().stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
entry -> parseIfIsFeelExpressionOrGetOriginal(entry.getValue())));
}
if (value instanceof String && isFeelExpression((String) value)) {
return parseExpression((String) value);
} else {
return value;
}
}

private static boolean isFeelExpression(String value) {
return value.startsWith("=");
}

private static Object parseExpression(String expressionStr) {
String feelExpression = expressionStr.substring(1);
Parsed<Exp> parsedExp = FeelParser.parseExpression(feelExpression);
if (!parsedExp.isSuccess()) {
throw new RuntimeException(parsedExp.toString());
}
Exp expression = parsedExp.get().value();
return parseExpression(expression);
}

private static Object parseExpression(final Exp expression) {
if (ConstNull.canEqual(expression)) {
return null;
} else if (expression instanceof ConstContext) {
ConstContext constContext = (ConstContext) expression;
Map<Object, Object> map = new HashMap<>();
for (int i = 0; i < constContext.entries().size(); i++) {
Tuple2<String, Exp> apply = constContext.entries().apply(i);
String s = apply._1;
Exp exp = apply._2;
map.put(s, parseExpression(exp));
}
return map;
} else if (expression instanceof ConstList) {
ConstList value1 = (ConstList) expression;
List<Exp> items = value1.items();
java.util.List<Object> result = new java.util.ArrayList<>();
for (int i = 0; i < items.size(); i++) {
result.add(parseExpression(items.apply(i)));
}
return result;
} else if (expression instanceof ArithmeticNegation) {
Object parsedValue = parseExpression(((ArithmeticNegation) expression).x());
return parsedValue != null ? ((BigDecimal) parsedValue).bigDecimal().negate() : null;
} else if (expression instanceof ConstString) {
return ((ConstString) expression).value();
} else if (expression instanceof ConstBool) {
return ((ConstBool) expression).value();
} else if (expression instanceof ConstNumber) {
return ((ConstNumber) expression).value();
} else if (expression instanceof ConstDate) {
return ((ConstDate) expression).value();
} else if (expression instanceof ConstDateTime) {
return ((ConstDateTime) expression).value();
} else if (expression instanceof ConstLocalDateTime) {
return ((ConstLocalDateTime) expression).value();
} else {
throw new RuntimeException("Failed to parse expression " + expression.toString());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,31 @@
import io.camunda.connector.api.inbound.InboundConnectorContext;
import io.camunda.connector.api.inbound.InboundConnectorResult;
import io.camunda.connector.api.secret.SecretProvider;
import io.camunda.connector.impl.Constants;
import io.camunda.connector.impl.context.AbstractConnectorContext;
import io.camunda.connector.impl.inbound.InboundConnectorProperties;
import io.camunda.connector.runtime.util.feel.FeelParserWrapper;
import io.camunda.connector.runtime.util.inbound.correlation.InboundCorrelationHandler;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class InboundConnectorContextImpl extends AbstractConnectorContext
implements InboundConnectorContext {
private final Logger LOG = LoggerFactory.getLogger(InboundConnectorContextImpl.class);
private static final Set<String> reservedKeys =
Set.of(
Constants.ACTIVATION_CONDITION_KEYWORD,
Constants.LEGACY_VARIABLE_MAPPING_KEYWORD,
Constants.INBOUND_TYPE_KEYWORD,
Constants.RESULT_VARIABLE_KEYWORD,
Constants.RESULT_EXPRESSION_KEYWORD,
Constants.ERROR_EXPRESSION_KEYWORD,
Constants.CORRELATION_KEY_EXPRESSION_KEYWORD);

private final InboundConnectorProperties properties;
private final InboundCorrelationHandler correlationHandler;
Expand Down Expand Up @@ -97,7 +111,21 @@ public InboundConnectorProperties getProperties() {

@Override
public <T> T getPropertiesAsType(Class<T> cls) {
return objectMapper.convertValue(properties.getPropertiesAsObjectMap(), cls);
Map<String, Object> result =
properties.getPropertiesAsObjectMap().entrySet().stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
entry ->
isReservedKey(entry.getKey())
? entry.getValue()
: FeelParserWrapper.parseIfIsFeelExpressionOrGetOriginal(
entry.getValue())));
return objectMapper.convertValue(result, cls);
}

private static boolean isReservedKey(final String value) {
return reservedKeys.contains(value);
}

@Override
Expand Down
Loading

0 comments on commit 1661850

Please sign in to comment.