This is an automated email from the ASF dual-hosted git repository.
fanjia pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/seatunnel-web.git
The following commit(s) were added to refs/heads/main by this push:
new ee399d28 [Bug][Seatunnel-web] Escape seatunnel-web placeholders (#225)
ee399d28 is described below
commit ee399d283b05ffc3fd191ca834f5f65928065b80
Author: Mohammad Arshad <[email protected]>
AuthorDate: Sat Oct 12 08:16:53 2024 +0530
[Bug][Seatunnel-web] Escape seatunnel-web placeholders (#225)
---
README_CN.md | 4 +-
.../framework/SeaTunnelOptionRuleWrapper.java | 21 +++++---
.../org/apache/seatunnel/app/utils/JobUtils.java | 19 +++++--
.../apache/seatunnel/app/utils/JobUtilsTests.java | 16 ++++++
.../app/dynamicforms/PlaceholderUtil.java | 46 +++++++++++++++++
.../app/dynamicforms/PlaceholderUtilTest.java | 60 ++++++++++++++++++++++
6 files changed, 153 insertions(+), 13 deletions(-)
diff --git a/README_CN.md b/README_CN.md
index d3e966c7..79339a94 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 (注意不要太短)。

@@ -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 (注意不要太短)。

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 4f5e7c60..41f307e9 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.FormLocale;
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 class SeaTunnelOptionRuleWrapper {
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<AbstractFormOption> wrapperOptionOptions(
@NonNull String connectorName, @NonNull List<Option<?>>
optionList, FormLocale locale) {
return optionList.stream()
@@ -401,10 +413,7 @@ public class SeaTunnelOptionRuleWrapper {
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 @@ public class SeaTunnelOptionRuleWrapper {
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 @@ public class SeaTunnelOptionRuleWrapper {
.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 b51db986..bf0f36d3 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 class JobUtils {
(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 dca59073..1d7f2725 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 class JobUtilsTests {
assertNotNull(config);
}
+ @Test
+ public void testEscapedPlaceholderValuesNotReplaced() {
+ String jobConfigContent =
+
"job.mode=\\${jobModeParam:BATCH}\ncheckpoint.interval=\\\\${checkParam:30}\njob.name=${jobNameParam}";
+ Map<String, String> 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<String, String> 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 00000000..c38209c1
--- /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("(?<!\\\\)\\$\\{(\\w+)(?::(.*?))?\\}");
+
+ /**
+ * To replace ${paramName:defaultValue} or ${paramName} with
\${paramName:defaultValue} and
+ * \${paramName} respectively.
+ *
+ * @param input the input string
+ * @return the input string with placeholders escaped
+ */
+ public static String escapePlaceholders(String input) {
+ Matcher matcher = placeholderPattern.matcher(input);
+ StringBuffer result = new StringBuffer();
+
+ while (matcher.find()) {
+ String placeholder = matcher.group();
+ matcher.appendReplacement(result, Matcher.quoteReplacement("\\" +
placeholder));
+ }
+
+ matcher.appendTail(result);
+ return result.toString();
+ }
+}
diff --git
a/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtilTest.java
b/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtilTest.java
new file mode 100644
index 00000000..4c88c7ac
--- /dev/null
+++
b/seatunnel-server/seatunnel-dynamicform/src/test/java/org/apache/seatunnel/app/dynamicforms/PlaceholderUtilTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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 org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PlaceholderUtilTest {
+
+ @Test
+ public void testEscapePlaceholders() {
+ String input = "This is a test string with ${paramName:defaultValue}
and ${paramName}.";
+ String expectedOutput =
+ "This is a test string with \\${paramName:defaultValue} and
\\${paramName}.";
+ String actualOutput = PlaceholderUtil.escapePlaceholders(input);
+ assertEquals(expectedOutput, actualOutput);
+ }
+
+ @Test
+ public void testEscapePlaceholdersWithNoPlaceholders() {
+ String input = "This is a test string with no placeholders.";
+ String expectedOutput = "This is a test string with no placeholders.";
+ String actualOutput = PlaceholderUtil.escapePlaceholders(input);
+ assertEquals(expectedOutput, actualOutput);
+ }
+
+ @Test
+ public void testEscapePlaceholdersWithEscapedPlaceholders() {
+ String input = "This is a test string with \\${paramName:defaultValue}
and \\${paramName}.";
+ String expectedOutput =
+ "This is a test string with \\${paramName:defaultValue} and
\\${paramName}.";
+ String actualOutput = PlaceholderUtil.escapePlaceholders(input);
+ assertEquals(expectedOutput, actualOutput);
+ }
+
+ @Test
+ public void testEscapePlaceholdersWithMixedPlaceholders() {
+ String input =
+ "This is a test string with ${paramName:defaultValue},
\\${paramName}, and ${anotherParam}.";
+ String expectedOutput =
+ "This is a test string with \\${paramName:defaultValue},
\\${paramName}, and \\${anotherParam}.";
+ String actualOutput = PlaceholderUtil.escapePlaceholders(input);
+ assertEquals(expectedOutput, actualOutput);
+ }
+}