This is an automated email from the ASF dual-hosted git repository.

peacewong pushed a commit to branch dev-1.2.0
in repository https://gitbox.apache.org/repos/asf/incubator-linkis.git


The following commit(s) were added to refs/heads/dev-1.2.0 by this push:
     new 523d2f747 support Variable Operation (#2415)
523d2f747 is described below

commit 523d2f747f41a3ff84a8827c0d7a86a684371448
Author: 野鹿 <[email protected]>
AuthorDate: Thu Jul 7 20:56:35 2022 +0800

    support Variable Operation (#2415)
    
    * Variable Operation
---
 .../VariableOperationFailedException.java          |  30 ++
 .../common/utils/VariableOperationUtils.java       | 317 +++++++++++++++++++++
 .../apache/linkis/common/conf/Configuration.scala  |   2 +
 .../apache/linkis/common/utils/VariableUtils.scala |  19 +-
 .../common/variable/VariableOperationTest.java     |  52 ++++
 5 files changed, 418 insertions(+), 2 deletions(-)

diff --git 
a/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/exception/VariableOperationFailedException.java
 
b/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/exception/VariableOperationFailedException.java
new file mode 100644
index 000000000..ed2ff207c
--- /dev/null
+++ 
b/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/exception/VariableOperationFailedException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.linkis.common.exception;
+
+public class VariableOperationFailedException extends ErrorException {
+
+    public VariableOperationFailedException(int errCode, String desc) {
+        super(errCode, desc);
+    }
+
+    public VariableOperationFailedException(int errCode, String desc, 
Exception e) {
+        super(errCode, desc);
+        this.initCause(e);
+    }
+}
diff --git 
a/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/utils/VariableOperationUtils.java
 
b/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/utils/VariableOperationUtils.java
new file mode 100644
index 000000000..39b98d134
--- /dev/null
+++ 
b/linkis-commons/linkis-common/src/main/java/org/apache/linkis/common/utils/VariableOperationUtils.java
@@ -0,0 +1,317 @@
+/*
+ * 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.linkis.common.utils;
+
+import org.apache.linkis.common.exception.VariableOperationFailedException;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Map;
+
+/** support variable operation ${yyyyMMdd%-1d}/${yyyy-MM-01%-2M} Date: 
2021/5/7 11:10 */
+public class VariableOperationUtils {
+
+    private static final String DOLLAR = "$";
+    private static final String PLACEHOLDER_SPLIT = "%";
+    private static final String PLACEHOLDER_LEFT = "{";
+    private static final String PLACEHOLDER_RIGHT = "}";
+    private static final String CYCLE_YEAR = "y";
+    private static final String CYCLE_MONTH = "M";
+    private static final String CYCLE_DAY = "d";
+    private static final String CYCLE_HOUR = "H";
+    private static final String CYCLE_MINUTE = "m";
+    private static final String CYCLE_SECOND = "s";
+    private static final String[] CYCLES =
+            new String[] {
+                CYCLE_YEAR, CYCLE_MONTH, CYCLE_DAY, CYCLE_HOUR, CYCLE_MINUTE, 
CYCLE_SECOND
+            };
+
+    /**
+     * yyyy-MM-dd HH:mm:ss
+     *
+     * @param date
+     * @return
+     */
+    public static ZonedDateTime toZonedDateTime(Date date) {
+        Instant instant = date.toInstant();
+        ZoneId zoneId = ZoneId.systemDefault();
+        LocalDateTime localDateTime = instant.atZone(zoneId).toLocalDateTime();
+        return ZonedDateTime.of(localDateTime, zoneId);
+    }
+
+    /**
+     * json support variable operation
+     *
+     * @param dateTime
+     * @param str
+     * @return
+     */
+    public static String replaces(ZonedDateTime dateTime, String str)
+            throws VariableOperationFailedException {
+        return replaces(dateTime, str, true);
+    }
+
+    /**
+     * json support variable operation
+     *
+     * @param dateTime
+     * @param str
+     * @param format
+     * @return
+     */
+    public static String replaces(ZonedDateTime dateTime, String str, boolean 
format)
+            throws VariableOperationFailedException {
+        try {
+            JsonNode rootNode = JsonUtils.jackson().readTree(str);
+            if (rootNode.isArray() || rootNode.isObject()) {
+                replaceJson(dateTime, rootNode);
+                return rootNode.toString();
+            }
+        } catch (Exception e) {
+            return replace(dateTime, str);
+        }
+        return replace(dateTime, str);
+    }
+
+    /**
+     * @param dateTime
+     * @param str
+     * @return
+     */
+    private static String replace(ZonedDateTime dateTime, String str)
+            throws VariableOperationFailedException {
+        StringBuilder buffer = new StringBuilder(str);
+        int startIndex = str.indexOf(PLACEHOLDER_LEFT);
+
+        while (startIndex != -1) {
+            int endIndex = buffer.indexOf(PLACEHOLDER_RIGHT, startIndex);
+            if (endIndex != -1) {
+                String placeHolder = buffer.substring(startIndex, endIndex + 
1);
+                String content =
+                        placeHolder
+                                .replace(PLACEHOLDER_LEFT, "")
+                                .replace(PLACEHOLDER_RIGHT, "")
+                                .trim();
+                String[] parts = content.split(PLACEHOLDER_SPLIT);
+                try {
+                    ZonedDateTime ndt = dateTime;
+                    for (int i = 1; i < parts.length; i++) {
+                        ndt = changeDateTime(ndt, parts[i]);
+                    }
+
+                    String newContent = 
ndt.format(DateTimeFormatter.ofPattern(parts[0]));
+                    if (buffer.substring(startIndex - 1, endIndex + 
1).contains(DOLLAR)) {
+                        buffer.replace(startIndex - 1, endIndex + 1, 
newContent);
+                    } else {
+                        buffer.replace(startIndex, endIndex + 1, newContent);
+                    }
+                    startIndex = buffer.indexOf(PLACEHOLDER_LEFT, startIndex + 
newContent.length());
+                } catch (IllegalArgumentException e1) {
+                    startIndex = buffer.indexOf(PLACEHOLDER_LEFT, endIndex);
+                } catch (Exception e2) {
+                    throw new VariableOperationFailedException(
+                            20050, "variable operation expression" + 
e2.getMessage(), e2);
+                }
+            } else {
+                startIndex = -1; // leave while
+            }
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * @param dateTime
+     * @param str
+     * @return
+     */
+    private static ZonedDateTime changeDateTime(ZonedDateTime dateTime, String 
str) {
+        if (str == null || str.isEmpty()) {
+            return dateTime;
+        }
+
+        for (String cycle : CYCLES) {
+            if (str.contains(cycle)) {
+                switch (cycle) {
+                    case CYCLE_DAY:
+                        return 
dateTime.plusDays(Integer.parseInt(str.replace(CYCLE_DAY, "")));
+                    case CYCLE_HOUR:
+                        return 
dateTime.plusHours(Integer.parseInt(str.replace(CYCLE_HOUR, "")));
+                    case CYCLE_MINUTE:
+                        return dateTime.plusMinutes(
+                                Integer.parseInt(str.replace(CYCLE_MINUTE, 
"")));
+                    case CYCLE_MONTH:
+                        return 
dateTime.plusMonths(Integer.parseInt(str.replace(CYCLE_MONTH, "")));
+                    case CYCLE_SECOND:
+                        return dateTime.plusSeconds(
+                                Integer.parseInt(str.replace(CYCLE_SECOND, 
"")));
+                    case CYCLE_YEAR:
+                        return 
dateTime.plusYears(Integer.parseInt(str.replace(CYCLE_YEAR, "")));
+                    default:
+                        break;
+                }
+            }
+        }
+
+        return dateTime;
+    }
+
+    /**
+     * @param keyValue
+     * @param str
+     * @return
+     */
+    private static String replace(Map<String, String> keyValue, String str)
+            throws VariableOperationFailedException {
+        StringBuilder buffer = new StringBuilder(str);
+        int startIndex = str.indexOf(PLACEHOLDER_LEFT);
+
+        while (startIndex != -1) {
+            int endIndex = buffer.indexOf(PLACEHOLDER_RIGHT, startIndex);
+            if (endIndex != -1) {
+                String placeHolder = buffer.substring(startIndex, endIndex + 
1);
+                String content =
+                        placeHolder
+                                .replace(PLACEHOLDER_LEFT, "")
+                                .replace(PLACEHOLDER_RIGHT, "")
+                                .trim();
+                try {
+                    String newContent = keyValue.get(content);
+                    if (newContent != null) {
+                        if (buffer.substring(startIndex - 1, endIndex + 
1).contains(DOLLAR)) {
+                            buffer.replace(startIndex - 1, endIndex + 1, 
newContent);
+                        } else {
+                            buffer.replace(startIndex, endIndex + 1, 
newContent);
+                        }
+                        startIndex =
+                                buffer.indexOf(PLACEHOLDER_LEFT, startIndex + 
newContent.length());
+                    } else {
+                        startIndex = buffer.indexOf(PLACEHOLDER_LEFT, 
endIndex);
+                    }
+                } catch (Exception e2) {
+                    throw new VariableOperationFailedException(
+                            20050, "variable operation expression" + 
e2.getMessage(), e2);
+                }
+            } else {
+                startIndex = -1; // leave while
+            }
+        }
+        return buffer.toString();
+    }
+
+    /**
+     * json support variable operation
+     *
+     * @param dateTime
+     * @param object
+     */
+    @SuppressWarnings("DuplicatedCode")
+    private static void replaceJson(ZonedDateTime dateTime, JsonNode object)
+            throws VariableOperationFailedException {
+        if (object.isArray()) {
+            ArrayNode arrayNode = (ArrayNode) object;
+            for (int i = 0; i < arrayNode.size(); i++) {
+                final JsonNode temp = arrayNode.get(i);
+                if (temp.isArray()) {
+                    replaceJson(dateTime, temp);
+                } else if (temp.isObject()) {
+                    replaceJson(dateTime, temp);
+                } else {
+                    arrayNode.insert(i, replace(dateTime, temp.toString()));
+                }
+            }
+        } else if (object.isObject()) {
+            ObjectNode objectNode = (ObjectNode) object;
+            final Iterator<Map.Entry<String, JsonNode>> fields = 
object.fields();
+            while (fields.hasNext()) {
+                final Map.Entry<String, JsonNode> field = fields.next();
+                final JsonNode temp = field.getValue();
+                if (temp.isArray()) {
+                    replaceJson(dateTime, temp);
+                } else if (temp.isObject()) {
+                    replaceJson(dateTime, temp);
+                } else {
+                    objectNode.put(field.getKey(), replace(dateTime, 
temp.toString()));
+                }
+            }
+        }
+    }
+
+    /**
+     * @param template
+     * @param map
+     * @return
+     */
+    public static String format(CharSequence template, Map<?, ?> map) {
+        return VariableOperationUtils.format(template, map, "${", "}", true);
+    }
+
+    /**
+     * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 
aValue and bValue
+     *
+     * @param template
+     * @param map
+     * @param leftStr
+     * @param rightStr
+     * @param ignoreNull
+     * @return
+     */
+    public static String format(
+            CharSequence template,
+            Map<?, ?> map,
+            CharSequence leftStr,
+            CharSequence rightStr,
+            boolean ignoreNull) {
+        if (null == template) {
+            return null;
+        }
+        if (null == map || map.isEmpty()) {
+            return template.toString();
+        }
+
+        if (StringUtils.isBlank(leftStr)) {
+            leftStr = "";
+        }
+
+        if (StringUtils.isBlank(rightStr)) {
+            rightStr = "";
+        }
+
+        String template2 = template.toString();
+        String value;
+        for (Map.Entry<?, ?> entry : map.entrySet()) {
+            value = entry.getValue().toString();
+            if (null == value && ignoreNull) {
+                continue;
+            }
+            template2 =
+                    StringUtils.replace(
+                            template2, leftStr.toString() + entry.getKey() + 
rightStr, value);
+        }
+        return template2;
+    }
+}
diff --git 
a/linkis-commons/linkis-common/src/main/scala/org/apache/linkis/common/conf/Configuration.scala
 
b/linkis-commons/linkis-common/src/main/scala/org/apache/linkis/common/conf/Configuration.scala
index f2696fab3..1b1a0528d 100644
--- 
a/linkis-commons/linkis-common/src/main/scala/org/apache/linkis/common/conf/Configuration.scala
+++ 
b/linkis-commons/linkis-common/src/main/scala/org/apache/linkis/common/conf/Configuration.scala
@@ -51,6 +51,8 @@ object Configuration extends Logging {
 
   val GOVERNANCE_STATION_ADMIN = 
CommonVars("wds.linkis.governance.station.admin", "hadoop")
 
+  val VARIABLE_OPERATION: Boolean = 
CommonVars("wds.linkis.variable.operation", true).getValue
+
   private val adminUsers = GOVERNANCE_STATION_ADMIN.getValue.split(",")
 
   def isAdmin(username: String): Boolean = {
diff --git 
a/linkis-commons/linkis-common/src/main/scala/org/apache/linkis/common/utils/VariableUtils.scala
 
b/linkis-commons/linkis-common/src/main/scala/org/apache/linkis/common/utils/VariableUtils.scala
index 4c7de924b..ac2a35bb3 100644
--- 
a/linkis-commons/linkis-common/src/main/scala/org/apache/linkis/common/utils/VariableUtils.scala
+++ 
b/linkis-commons/linkis-common/src/main/scala/org/apache/linkis/common/utils/VariableUtils.scala
@@ -18,11 +18,13 @@
 package org.apache.linkis.common.utils
 
 import org.apache.commons.lang3.StringUtils
+import org.apache.linkis.common.conf.Configuration
 import org.apache.linkis.common.exception.LinkisCommonErrorException
 import org.apache.linkis.common.variable
 import org.apache.linkis.common.variable.DateTypeUtils.{getCurHour, 
getMonthDay, getToday, getYesterday}
 import org.apache.linkis.common.variable._
 
+import java.time.ZonedDateTime
 import java.util
 import scala.collection.JavaConverters.mapAsScalaMapConverter
 import scala.collection.convert.WrapAsScala._
@@ -62,8 +64,10 @@ object VariableUtils extends Logging {
       run_date = new CustomDateType(getYesterday(false), false)
       nameAndType(RUN_DATE) = variable.DateType(new 
CustomDateType(run_date.toString, false))
     }
+
     initAllDateVars(run_date, nameAndType)
-    parserVar(replaceStr, nameAndType)
+    val codeOperation = parserDate(replaceStr, run_date)
+    parserVar(codeOperation, nameAndType)
   }
 
   def replace(code: String, runtType: String, variables: util.Map[String, 
String]): String = {
@@ -116,9 +120,20 @@ object VariableUtils extends Logging {
     }
 
     initAllDateVars(run_date, nameAndType)
-    parserVar(code, nameAndType)
+    val codeOperation = parserDate(code, run_date)
+    parserVar(codeOperation, nameAndType)
   }
 
+  private def parserDate(code: String, run_date: CustomDateType) : String = {
+    if (Configuration.VARIABLE_OPERATION) {
+      val zonedDateTime: ZonedDateTime = 
VariableOperationUtils.toZonedDateTime(run_date.getDate)
+      VariableOperationUtils.replaces(zonedDateTime, code)
+    } else {
+      code
+    }
+  }
+
+
   private def initAllDateVars(run_date: CustomDateType, nameAndType: 
mutable.Map[String, variable.VariableType]): Unit = {
     val run_date_str = run_date.toString
     nameAndType("run_date_std") = variable.DateType(new 
CustomDateType(run_date.getStdDate))
diff --git 
a/linkis-commons/linkis-common/src/test/java/org/apache/linkis/common/variable/VariableOperationTest.java
 
b/linkis-commons/linkis-common/src/test/java/org/apache/linkis/common/variable/VariableOperationTest.java
new file mode 100644
index 000000000..e8485aa46
--- /dev/null
+++ 
b/linkis-commons/linkis-common/src/test/java/org/apache/linkis/common/variable/VariableOperationTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.linkis.common.variable;
+
+import org.apache.linkis.common.exception.VariableOperationFailedException;
+import org.apache.linkis.common.utils.VariableOperationUtils;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.ZonedDateTime;
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class VariableOperationTest {
+
+    private static final Date date = new Date(1648892107169L);
+    private static final ZonedDateTime zonedDateTime = 
VariableOperationUtils.toZonedDateTime(date);
+
+    @Test
+    public void testJsonFormat() throws VariableOperationFailedException {
+        String jsonOld =
+                
"{\"name\":\"${yyyyMMdd%-1d}\",\"address\":{\"street\":\"${yyyyMMdd%-1y}\"},\"links\":[{\"name\":\"${yyyyMMdd%-1M}\"}]}";
+        String jsonNew = VariableOperationUtils.replaces(zonedDateTime, 
jsonOld);
+        System.out.println(jsonOld + "\n" + jsonNew);
+        assertEquals(
+                jsonNew,
+                
"{\"name\":\"\\\"20220401\\\"\",\"address\":{\"street\":\"\\\"20210402\\\"\"},\"links\":[{\"name\":\"\\\"20220302\\\"\"}]}");
+    }
+
+    @Test
+    public void testTextFormat() throws VariableOperationFailedException {
+        String strOld = "abc${yyyyMMdd%-1d}def";
+        String strNew = VariableOperationUtils.replaces(zonedDateTime, strOld);
+        System.out.println(strOld + "\n" + strNew);
+        assertEquals(strNew, "abc20220401def");
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to