From ee399d283b05ffc3fd191ca834f5f65928065b80 Mon Sep 17 00:00:00 2001 From: Mohammad Arshad Date: Sat, 12 Oct 2024 08:16:53 +0530 Subject: [PATCH] [Bug][Seatunnel-web] Escape seatunnel-web placeholders (#225) --- README_CN.md | 4 +- .../framework/SeaTunnelOptionRuleWrapper.java | 21 +++++-- .../apache/seatunnel/app/utils/JobUtils.java | 19 ++++-- .../seatunnel/app/utils/JobUtilsTests.java | 16 +++++ .../app/dynamicforms/PlaceholderUtil.java | 46 ++++++++++++++ .../app/dynamicforms/PlaceholderUtilTest.java | 60 +++++++++++++++++++ 6 files changed, 153 insertions(+), 13 deletions(-) create mode 100644 seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtil.java create mode 100644 seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtilTest.java diff --git a/README_CN.md b/README_CN.md index d3e966c7f..79339a940 100644 --- a/README_CN.md +++ b/README_CN.md @@ -73,7 +73,7 @@ sh build.sh code #### 2.4 配置应用程序并运行SeaTunnel Web后端服务器 1. 编辑 `seatunnel-server/seatunnel-app/src/main/resources/application.yml` 写数据库连接信息 -2. 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 文件,填写jwt.secretKey密钥,例如:https://github.com/apache/seatunnel(注意不要太短)。 +2. 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 文件,填写jwt.secretKey密钥,例如:https://github.com/apache/seatunnel (注意不要太短)。 ![img.png](docs/images/application_config.png) @@ -173,7 +173,7 @@ tar -zxvf apache-seatunnel-web-${project.version}.tar.gz #### 3.5 配置应用并运行 SeaTunnel Web 后端服务 * 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 在文件中填写数据库连接信息和数据服务接口相关信息。 -* 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 文件,填写jwt.secretKey密钥,例如:https://github.com/apache/seatunnel(注意不要太短)。 +* 编辑 `apache-seatunnel-web-${project.version}/conf/application.yml` 文件,填写jwt.secretKey密钥,例如:https://github.com/apache/seatunnel (注意不要太短)。 ![image](docs/images/application_config.png) diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/thirdparty/framework/SeaTunnelOptionRuleWrapper.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/thirdparty/framework/SeaTunnelOptionRuleWrapper.java index 4f5e7c609..41f307e9a 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/thirdparty/framework/SeaTunnelOptionRuleWrapper.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/thirdparty/framework/SeaTunnelOptionRuleWrapper.java @@ -27,6 +27,7 @@ import org.apache.seatunnel.app.dynamicforms.FormOptionBuilder; import org.apache.seatunnel.app.dynamicforms.FormStructure; import org.apache.seatunnel.app.dynamicforms.FormStructureBuilder; +import org.apache.seatunnel.app.dynamicforms.PlaceholderUtil; import org.apache.seatunnel.app.dynamicforms.validate.ValidateBuilder; import org.apache.seatunnel.common.constants.PluginType; @@ -97,6 +98,17 @@ public static FormStructure wrapper( return FormOptionSort.sortFormStructure(formStructureBuilder.build()); } + private static Object getDefaultValue(Option option) { + Object defValue = option.defaultValue(); + if (defValue == null) { + return null; + } + if (String.class.equals(option.typeReference().getType())) { + return PlaceholderUtil.escapePlaceholders(defValue.toString()); + } + return defValue; + } + private static List wrapperOptionOptions( @NonNull String connectorName, @NonNull List> optionList, FormLocale locale) { return optionList.stream() @@ -401,10 +413,7 @@ private static AbstractFormOption selectInput( AbstractFormOption abstractFormOption = staticSelectOptionBuilder .formStaticSelectOption() - .withDefaultValue( - option.defaultValue() == null - ? null - : option.defaultValue().toString()); + .withDefaultValue(getDefaultValue(option)); String placeholderI18nOptionKey = i18nOptionKey + "_description"; if (enableLabelI18n(connectorName, placeholderI18nOptionKey, locale)) { @@ -457,7 +466,7 @@ private static AbstractFormOption textInput( builder.withField(option.key()) .inputOptionBuilder() .formTextInputOption() - .withDefaultValue(option.defaultValue()); + .withDefaultValue(getDefaultValue(option)); if (enableLabelI18n(connectorName, placeholderI18nOptionKey, locale)) { abstractFormOption = abstractFormOption.withI18nPlaceholder(placeholderI18nOptionKey); } else { @@ -484,7 +493,7 @@ private static AbstractFormOption textareaInput( .inputOptionBuilder() .formTextareaInputOption() .withClearable() - .withDefaultValue(option.defaultValue()); + .withDefaultValue(getDefaultValue(option)); if (enableLabelI18n(connectorName, placeholderI18nOptionKey, locale)) { abstractFormOption = abstractFormOption.withI18nPlaceholder(placeholderI18nOptionKey); } else { diff --git a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/JobUtils.java b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/JobUtils.java index b51db986a..bf0f36d33 100644 --- a/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/JobUtils.java +++ b/seatunnel-server/seatunnel-app/src/main/java/org/apache/seatunnel/app/utils/JobUtils.java @@ -32,7 +32,8 @@ public class JobUtils { // The maximum length of the job execution error message, 4KB private static final int ERROR_MESSAGE_MAX_LENGTH = 4096; - private static final Pattern placeholderPattern = Pattern.compile("\\$\\{(\\w+)(?::(.*?))?\\}"); + private static final Pattern placeholderPattern = + Pattern.compile("(\\\\{0,2})\\$\\{(\\w+)(?::(.*?))?\\}"); public static String getJobInstanceErrorMessage(String message) { if (message == null) { @@ -75,18 +76,26 @@ public static String replaceJobConfigPlaceholders( (jobExecParam != null && jobExecParam.getPlaceholderValues() != null) ? jobExecParam.getPlaceholderValues() : Collections.emptyMap(); - Matcher matcher = placeholderPattern.matcher(jobConfigString); StringBuffer result = new StringBuffer(); while (matcher.find()) { - String placeholderName = matcher.group(1); - String replacement = placeholderValues.getOrDefault(placeholderName, matcher.group(2)); + String escapeCharacter = matcher.group(1); + String placeholderName = matcher.group(2); + + if (escapeCharacter != null && !escapeCharacter.isEmpty()) { + String withoutEscape = + matcher.group().replace("\\\\${", "${").replace("\\${", "${"); + matcher.appendReplacement(result, Matcher.quoteReplacement(withoutEscape)); + // remove the escape character and continue + continue; + } + String replacement = placeholderValues.getOrDefault(placeholderName, matcher.group(3)); if (replacement == null) { throw new SeatunnelException( SeatunnelErrorEnum.JOB_NO_VALUE_FOUND_FOR_PLACEHOLDER, placeholderName); } - matcher.appendReplacement(result, replacement); + matcher.appendReplacement(result, Matcher.quoteReplacement(replacement)); } matcher.appendTail(result); diff --git a/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/utils/JobUtilsTests.java b/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/utils/JobUtilsTests.java index dca590731..1d7f27256 100644 --- a/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/utils/JobUtilsTests.java +++ b/seatunnel-server/seatunnel-app/src/test/java/org/apache/seatunnel/app/utils/JobUtilsTests.java @@ -109,6 +109,22 @@ public void testParseConfigWithPlaceHolders() { assertNotNull(config); } + @Test + public void testEscapedPlaceholderValuesNotReplaced() { + String jobConfigContent = + "job.mode=\\${jobModeParam:BATCH}\ncheckpoint.interval=\\\\${checkParam:30}\njob.name=${jobNameParam}"; + Map paramValues = new HashMap<>(); + paramValues.put("jobModeParam", "STREAMING"); + paramValues.put("jobNameParam", "newJob"); + JobExecParam jobExecParam = getJobExecParam(paramValues); + + String expected = + "job.mode=${jobModeParam:BATCH}\ncheckpoint.interval=${checkParam:30}\njob.name=newJob"; + String actual = JobUtils.replaceJobConfigPlaceholders(jobConfigContent, jobExecParam); + + assertEquals(expected, actual); + } + private JobExecParam getJobExecParam(Map paramValues) { JobExecParam jobExecParam = new JobExecParam(); jobExecParam.setPlaceholderValues(paramValues); diff --git a/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtil.java b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtil.java new file mode 100644 index 000000000..c38209c1e --- /dev/null +++ b/seatunnel-server/seatunnel-dynamicform/src/main/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtil.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); 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 org.apache.seatunnel.app.dynamicforms; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class PlaceholderUtil { + + private static final Pattern placeholderPattern = + Pattern.compile("(?