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

benjobs pushed a commit to branch dev-2.1.5
in repository https://gitbox.apache.org/repos/asf/incubator-streampark.git


The following commit(s) were added to refs/heads/dev-2.1.5 by this push:
     new 47abdca3b [Improve] openAPI backend service layer improvement
47abdca3b is described below

commit 47abdca3b5aaf9e1937ab9fb285bb3677881f7f2
Author: benjobs <[email protected]>
AuthorDate: Fri Jul 26 14:39:24 2024 +0800

    [Improve] openAPI backend service layer improvement
---
 .../streampark/common/util/CURLBuilder.scala       |  19 +-
 .../streampark/common/util/ReflectUtils.scala      |  26 ++-
 .../apache/streampark/console/base/util/Tuple.java |  62 ++++++
 .../streampark/console/base/util/Tuple1.java       | 113 +++++++++++
 .../streampark/console/base/util/Tuple2.java       | 163 +++++++++++++++
 .../streampark/console/base/util/Tuple3.java       | 171 ++++++++++++++++
 .../streampark/console/base/util/Tuple4.java       | 187 ++++++++++++++++++
 .../annotation/{ApiAccess.java => OpenAPI.java}    |  24 ++-
 .../console/core/aspect/StreamParkAspect.java      |   6 +-
 .../ApiAccess.java => bean/OpenAPISchema.java}     |  43 +++-
 .../console/core/component/OpenAPIComponent.java   | 218 +++++++++++++++++++++
 .../core/controller/ApplicationController.java     |   2 -
 .../console/core/controller/OpenAPIController.java | 156 +++++++++++++++
 13 files changed, 1158 insertions(+), 32 deletions(-)

diff --git 
a/streampark-common/src/main/scala/org/apache/streampark/common/util/CURLBuilder.scala
 
b/streampark-common/src/main/scala/org/apache/streampark/common/util/CURLBuilder.scala
index ef8bde57d..a35db77cd 100644
--- 
a/streampark-common/src/main/scala/org/apache/streampark/common/util/CURLBuilder.scala
+++ 
b/streampark-common/src/main/scala/org/apache/streampark/common/util/CURLBuilder.scala
@@ -17,35 +17,32 @@
 package org.apache.streampark.common.util
 
 import java.util
+import java.util.{HashMap => JavaHashMap}
 
 import scala.collection.JavaConversions._
 
 class CURLBuilder(val url: String) {
 
-  val headers: util.Map[String, String] = new util.HashMap[String, String]
+  private[this] val headers: util.Map[String, String] = new 
JavaHashMap[String, String]
 
-  val formDatas: util.Map[String, String] = new util.HashMap[String, String]
+  private[this] val formData: util.Map[String, String] = new 
JavaHashMap[String, String]
 
   def addHeader(k: String, v: String): CURLBuilder = {
     this.headers.put(k, v)
     this
   }
 
-  def addFormData(k: String, v: String): CURLBuilder = {
-    this.formDatas.put(k, v)
+  def addFormData(k: String, v: java.io.Serializable): CURLBuilder = {
+    this.formData.put(k, v.toString)
     this
   }
 
   def build: String = {
-    require(url != null, "[StreamPark] cURL build failed, url must not be 
null")
+    require(url != null, "[StreamPark] CURL build failed, url must not be 
null")
     val cURL = new StringBuilder("curl -X POST ")
     cURL.append(String.format("'%s' \\\n", url))
-    for (headerKey <- headers.keySet) {
-      cURL.append(String.format("-H \'%s: %s\' \\\n", headerKey, 
headers.get(headerKey)))
-    }
-    for (field <- formDatas.keySet) {
-      cURL.append(String.format("--data-urlencode \'%s=%s\' \\\n", field, 
formDatas.get(field)))
-    }
+    headers.keySet.foreach(h => cURL.append(String.format("-H \'%s: %s\' 
\\\n", h, headers.get(h))))
+    formData.foreach(k => cURL.append(String.format("--data-urlencode 
\'%s=%s\' \\\n", k._1, k._2)))
     cURL.append("-i")
     cURL.toString
   }
diff --git 
a/streampark-common/src/main/scala/org/apache/streampark/common/util/ReflectUtils.scala
 
b/streampark-common/src/main/scala/org/apache/streampark/common/util/ReflectUtils.scala
index ef2deefc4..3a5183388 100644
--- 
a/streampark-common/src/main/scala/org/apache/streampark/common/util/ReflectUtils.scala
+++ 
b/streampark-common/src/main/scala/org/apache/streampark/common/util/ReflectUtils.scala
@@ -19,9 +19,11 @@ package org.apache.streampark.common.util
 
 import org.apache.commons.lang3.StringUtils
 
-import java.lang.reflect.{Field, Modifier}
+import java.lang.annotation.Annotation
+import java.lang.reflect.{Field, Method, Modifier}
 import java.util.Objects
 
+import scala.collection.JavaConverters._
 import scala.util.{Failure, Success, Try}
 
 object ReflectUtils extends Logger {
@@ -40,7 +42,10 @@ object ReflectUtils extends Logger {
    */
   @throws[SecurityException]
   def getField(beanClass: Class[_], name: String): Field = {
-    Try(beanClass.getDeclaredFields.filter(f => Objects.equals(name, 
f.getName)).head)
+    Try(
+      beanClass.getDeclaredFields
+        .filter(f => Objects.equals(name, f.getName))
+        .head)
       .getOrElse(null)
   }
 
@@ -50,8 +55,9 @@ object ReflectUtils extends Logger {
   }
 
   def getFieldValue(obj: Any, field: Field): Any = {
-    if (obj == null || field == null) null
-    else {
+    if (obj == null || field == null) {
+      null
+    } else {
       field.setAccessible(true)
       field.get(obj) match {
         case Success(v) => v
@@ -62,11 +68,11 @@ object ReflectUtils extends Logger {
 
   def setFieldValue(obj: Any, fieldName: String, value: Any): Unit = {
     val field = getAccessibleField(obj, fieldName)
-    if (field == null)
+    if (field == null) {
       throw new IllegalArgumentException(
         "Could not find field [" + fieldName + "] on target [" + obj + "]")
-    try
-      field.set(obj, value)
+    }
+    try field.set(obj, value)
     catch {
       case e: IllegalAccessException =>
         logError("Failed to assign to the element.", e)
@@ -101,4 +107,10 @@ object ReflectUtils extends Logger {
     }
   }
 
+  def getMethodsByAnnotation(
+      beanClass: Class[_],
+      annotClazz: Class[_ <: Annotation]): java.util.List[Method] = {
+    beanClass.getDeclaredMethods.filter(_.getDeclaredAnnotation(annotClazz) != 
null).toList.asJava
+  }
+
 }
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple.java
new file mode 100644
index 000000000..f80d6bd38
--- /dev/null
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple.java
@@ -0,0 +1,62 @@
+/*
+ * 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.streampark.console.base.util;
+
+/**
+ * The base class of all tuples. Tuples have a fix length and contain a set of 
fields, which may all
+ * be of different types. Because Tuples are strongly typed, each distinct 
tuple length is
+ * represented by its own class. Tuples exists with up to 25 fields and are 
described in the classes
+ * {@link Tuple1} to {@link Tuple4}.
+ *
+ * <p>The fields in the tuples may be accessed directly a public fields, or 
via position (zero
+ * indexed) {@link #get(int)}.
+ *
+ * <p>Tuples are in principle serializable. However, they may contain 
non-serializable fields, in
+ * which case serialization will fail.
+ */
+public abstract class Tuple implements java.io.Serializable {
+
+  private static final long serialVersionUID = 1L;
+
+  /**
+   * Gets the field at the specified position.
+   *
+   * @param pos The position of the field, zero indexed.
+   * @return The field at the specified position.
+   * @throws IndexOutOfBoundsException Thrown, if the position is negative, or 
equal to, or larger
+   *     than the number of fields.
+   */
+  public abstract <T> T get(int pos);
+
+  /**
+   * Sets the field at the specified position.
+   *
+   * @param value The value to be assigned to the field at the specified 
position.
+   * @param pos The position of the field, zero indexed.
+   * @throws IndexOutOfBoundsException Thrown, if the position is negative, or 
equal to, or larger
+   *     than the number of fields.
+   */
+  public abstract <T> void set(T value, int pos);
+
+  /**
+   * Shallow tuple copy.
+   *
+   * @return A new Tuple with the same fields as this.
+   */
+  public abstract <T extends Tuple> T copy();
+}
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple1.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple1.java
new file mode 100644
index 000000000..1a6f79d0b
--- /dev/null
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple1.java
@@ -0,0 +1,113 @@
+/*
+ * 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.streampark.console.base.util;
+
+import java.util.Objects;
+
+public class Tuple1<T0> extends Tuple {
+
+  private static final long serialVersionUID = 1L;
+
+  /** Field 0 of the tuple. */
+  public T0 t1;
+
+  /** Creates a new tuple where all fields are null. */
+  public Tuple1() {}
+
+  /**
+   * Creates a new tuple and assigns the given values to the tuple's fields.
+   *
+   * @param t0 The value for field 0
+   */
+  public Tuple1(T0 t0) {
+    this.t1 = t0;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T> T get(int pos) {
+    if (pos == 0) {
+      return (T) this.t1;
+    }
+    throw new IndexOutOfBoundsException(String.valueOf(pos));
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T> void set(T value, int pos) {
+    if (pos == 0) {
+      this.t1 = (T0) value;
+    } else {
+      throw new IndexOutOfBoundsException(String.valueOf(pos));
+    }
+  }
+
+  /**
+   * Sets new values to all fields of the tuple.
+   *
+   * @param f0 The value for field 0
+   */
+  public void set(T0 f0) {
+    this.t1 = f0;
+  }
+
+  /**
+   * Deep equality for tuples by calling equals() on the tuple members.
+   *
+   * @param o the object checked for equality
+   * @return true if this is equal to o.
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Tuple1)) {
+      return false;
+    }
+    @SuppressWarnings("rawtypes")
+    Tuple1 tuple = (Tuple1) o;
+    return Objects.equals(t1, tuple.t1);
+  }
+
+  @Override
+  public int hashCode() {
+    return t1 != null ? t1.hashCode() : 0;
+  }
+
+  /**
+   * Shallow tuple copy.
+   *
+   * @return A new Tuple with the same fields as this.
+   */
+  @Override
+  @SuppressWarnings("unchecked")
+  public Tuple1<T0> copy() {
+    return new Tuple1<>(this.t1);
+  }
+
+  /**
+   * Creates a new tuple and assigns the given values to the tuple's fields. 
This is more convenient
+   * than using the constructor, because the compiler can infer the generic 
type arguments
+   * implicitly. For example: {@code Tuple3.of(n, x, s)} instead of {@code new 
Tuple3<Integer,
+   * Double, String>(n, x, s)}
+   */
+  public static <T0> Tuple1<T0> of(T0 f0) {
+    return new Tuple1<>(f0);
+  }
+}
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple2.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple2.java
new file mode 100644
index 000000000..886beb113
--- /dev/null
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple2.java
@@ -0,0 +1,163 @@
+/*
+ * 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.streampark.console.base.util;
+
+import java.util.Objects;
+
+/**
+ * A tuple with 2 fields. Tuples are strongly typed; each field may be of a 
separate type. The
+ * fields of the tuple can be accessed directly as public fields (f0, f1, ...) 
or via their position
+ * through the {@link #get(int)} method. The tuple field positions start at 
zero.
+ *
+ * <p>Tuples are mutable types, meaning that their fields can be re-assigned. 
This allows functions
+ * that work with Tuples to reuse objects in order to reduce pressure on the 
garbage collector.
+ *
+ * <p>Warning: If you subclass Tuple2, then be sure to either
+ *
+ * <ul>
+ *   <li>not add any new fields, or
+ *   <li>make it a POJO, and always declare the element type of your 
DataStreams/DataSets to your
+ *       descendant type. (That is, if you have a "class Foo extends Tuple2", 
then don't use
+ *       instances of Foo in a DataStream&lt;Tuple2&gt; / 
DataSet&lt;Tuple2&gt;, but declare it as
+ *       DataStream&lt;Foo&gt; / DataSet&lt;Foo&gt;.)
+ * </ul>
+ *
+ * @see Tuple
+ * @param <T0> The type of field 0
+ * @param <T1> The type of field 1
+ */
+public class Tuple2<T0, T1> extends Tuple {
+
+  private static final long serialVersionUID = 1L;
+
+  /** Field 0 of the tuple. */
+  public T0 t1;
+  /** Field 1 of the tuple. */
+  public T1 t2;
+
+  /** Creates a new tuple where all fields are null. */
+  public Tuple2() {}
+
+  /**
+   * Creates a new tuple and assigns the given values to the tuple's fields.
+   *
+   * @param t0 The value for field 0
+   * @param t1 The value for field 1
+   */
+  public Tuple2(T0 t0, T1 t1) {
+    this.t1 = t0;
+    this.t2 = t1;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T> T get(int pos) {
+    switch (pos) {
+      case 0:
+        return (T) this.t1;
+      case 1:
+        return (T) this.t2;
+      default:
+        throw new IndexOutOfBoundsException(String.valueOf(pos));
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  public <T> void set(T value, int pos) {
+    switch (pos) {
+      case 0:
+        this.t1 = (T0) value;
+        break;
+      case 1:
+        this.t2 = (T1) value;
+        break;
+      default:
+        throw new IndexOutOfBoundsException(String.valueOf(pos));
+    }
+  }
+
+  /**
+   * Sets new values to all fields of the tuple.
+   *
+   * @param f0 The value for field 0
+   * @param f1 The value for field 1
+   */
+  public void set(T0 f0, T1 f1) {
+    this.t1 = f0;
+    this.t2 = f1;
+  }
+
+  /**
+   * Returns a shallow copy of the tuple with swapped values.
+   *
+   * @return shallow copy of the tuple with swapped values
+   */
+  public Tuple2<T1, T0> swap() {
+    return new Tuple2<T1, T0>(t2, t1);
+  }
+
+  /**
+   * Deep equality for tuples by calling equals() on the tuple members.
+   *
+   * @param o the object checked for equality
+   * @return true if this is equal to o.
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Tuple2)) {
+      return false;
+    }
+    @SuppressWarnings("rawtypes")
+    Tuple2 tuple = (Tuple2) o;
+    if (!Objects.equals(t1, tuple.t1)) {
+      return false;
+    }
+    return Objects.equals(t2, tuple.t2);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = t1 != null ? t1.hashCode() : 0;
+    result = 31 * result + (t2 != null ? t2.hashCode() : 0);
+    return result;
+  }
+
+  /**
+   * Shallow tuple copy.
+   *
+   * @return A new Tuple with the same fields as this.
+   */
+  @Override
+  @SuppressWarnings("unchecked")
+  public Tuple2<T0, T1> copy() {
+    return new Tuple2<>(this.t1, this.t2);
+  }
+
+  /**
+   * Creates a new tuple and assigns the given values to the tuple's fields. 
This is more convenient
+   * than using the constructor, because the compiler can infer the generic 
type arguments
+   * implicitly. For example: {@code Tuple3.of(n, x, s)} instead of {@code new 
Tuple3<Integer,
+   * Double, String>(n, x, s)}
+   */
+  public static <T0, T1> Tuple2<T0, T1> of(T0 f0, T1 f1) {
+    return new Tuple2<>(f0, f1);
+  }
+}
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple3.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple3.java
new file mode 100644
index 000000000..802262418
--- /dev/null
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple3.java
@@ -0,0 +1,171 @@
+/*
+ * 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.streampark.console.base.util;
+
+import java.util.Objects;
+
+/**
+ * A tuple with 3 fields. Tuples are strongly typed; each field may be of a 
separate type. The
+ * fields of the tuple can be accessed directly as public fields (f0, f1, ...) 
or via their position
+ * through the {@link #get(int)} method. The tuple field positions start at 
zero.
+ *
+ * <p>Tuples are mutable types, meaning that their fields can be re-assigned. 
This allows functions
+ * that work with Tuples to reuse objects in order to reduce pressure on the 
garbage collector.
+ *
+ * <p>Warning: If you subclass Tuple3, then be sure to either
+ *
+ * <ul>
+ *   <li>not add any new fields, or
+ *   <li>make it a POJO, and always declare the element type of your 
DataStreams/DataSets to your
+ *       descendant type. (That is, if you have a "class Foo extends Tuple3", 
then don't use
+ *       instances of Foo in a DataStream&lt;Tuple3&gt; / 
DataSet&lt;Tuple3&gt;, but declare it as
+ *       DataStream&lt;Foo&gt; / DataSet&lt;Foo&gt;.)
+ * </ul>
+ *
+ * @see Tuple
+ * @param <T0> The type of field 0
+ * @param <T1> The type of field 1
+ * @param <T2> The type of field 2
+ */
+public class Tuple3<T0, T1, T2> extends Tuple {
+
+  private static final long serialVersionUID = 1L;
+
+  /** Field 0 of the tuple. */
+  public T0 t1;
+  /** Field 1 of the tuple. */
+  public T1 t2;
+  /** Field 2 of the tuple. */
+  public T2 t3;
+
+  /** Creates a new tuple where all fields are null. */
+  public Tuple3() {}
+
+  /**
+   * Creates a new tuple and assigns the given values to the tuple's fields.
+   *
+   * @param t0 The value for field 0
+   * @param t1 The value for field 1
+   * @param t2 The value for field 2
+   */
+  public Tuple3(T0 t0, T1 t1, T2 t2) {
+    this.t1 = t0;
+    this.t2 = t1;
+    this.t3 = t2;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T> T get(int pos) {
+    switch (pos) {
+      case 0:
+        return (T) this.t1;
+      case 1:
+        return (T) this.t2;
+      case 2:
+        return (T) this.t3;
+      default:
+        throw new IndexOutOfBoundsException(String.valueOf(pos));
+    }
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T> void set(T value, int pos) {
+    switch (pos) {
+      case 0:
+        this.t1 = (T0) value;
+        break;
+      case 1:
+        this.t2 = (T1) value;
+        break;
+      case 2:
+        this.t3 = (T2) value;
+        break;
+      default:
+        throw new IndexOutOfBoundsException(String.valueOf(pos));
+    }
+  }
+
+  /**
+   * Sets new values to all fields of the tuple.
+   *
+   * @param f0 The value for field 0
+   * @param f1 The value for field 1
+   * @param f2 The value for field 2
+   */
+  public void set(T0 f0, T1 f1, T2 f2) {
+    this.t1 = f0;
+    this.t2 = f1;
+    this.t3 = f2;
+  }
+
+  /**
+   * Deep equality for tuples by calling equals() on the tuple members.
+   *
+   * @param o the object checked for equality
+   * @return true if this is equal to o.
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Tuple3)) {
+      return false;
+    }
+    @SuppressWarnings("rawtypes")
+    Tuple3 tuple = (Tuple3) o;
+    if (!Objects.equals(t1, tuple.t1)) {
+      return false;
+    }
+    if (!Objects.equals(t2, tuple.t2)) {
+      return false;
+    }
+    return Objects.equals(t3, tuple.t3);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = t1 != null ? t1.hashCode() : 0;
+    result = 31 * result + (t2 != null ? t2.hashCode() : 0);
+    result = 31 * result + (t3 != null ? t3.hashCode() : 0);
+    return result;
+  }
+
+  /**
+   * Shallow tuple copy.
+   *
+   * @return A new Tuple with the same fields as this.
+   */
+  @Override
+  @SuppressWarnings("unchecked")
+  public Tuple3<T0, T1, T2> copy() {
+    return new Tuple3<>(this.t1, this.t2, this.t3);
+  }
+
+  /**
+   * Creates a new tuple and assigns the given values to the tuple's fields. 
This is more convenient
+   * than using the constructor, because the compiler can infer the generic 
type arguments
+   * implicitly. For example: {@code Tuple3.of(n, x, s)} instead of {@code new 
Tuple3<Integer,
+   * Double, String>(n, x, s)}
+   */
+  public static <T0, T1, T2> Tuple3<T0, T1, T2> of(T0 f0, T1 f1, T2 f2) {
+    return new Tuple3<>(f0, f1, f2);
+  }
+}
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple4.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple4.java
new file mode 100644
index 000000000..0296d33e5
--- /dev/null
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/Tuple4.java
@@ -0,0 +1,187 @@
+/*
+ * 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.streampark.console.base.util;
+
+import java.util.Objects;
+
+/**
+ * A tuple with 4 fields. Tuples are strongly typed; each field may be of a 
separate type. The
+ * fields of the tuple can be accessed directly as public fields (f0, f1, ...) 
or via their position
+ * through the {@link #get(int)} method. The tuple field positions start at 
zero.
+ *
+ * <p>Tuples are mutable types, meaning that their fields can be re-assigned. 
This allows functions
+ * that work with Tuples to reuse objects in order to reduce pressure on the 
garbage collector.
+ *
+ * <p>Warning: If you subclass Tuple4, then be sure to either
+ *
+ * <ul>
+ *   <li>not add any new fields, or
+ *   <li>make it a POJO, and always declare the element type of your 
DataStreams/DataSets to your
+ *       descendant type. (That is, if you have a "class Foo extends Tuple4", 
then don't use
+ *       instances of Foo in a DataStream&lt;Tuple4&gt; / 
DataSet&lt;Tuple4&gt;, but declare it as
+ *       DataStream&lt;Foo&gt; / DataSet&lt;Foo&gt;.)
+ * </ul>
+ *
+ * @see Tuple
+ * @param <T0> The type of field 0
+ * @param <T1> The type of field 1
+ * @param <T2> The type of field 2
+ * @param <T3> The type of field 3
+ */
+public class Tuple4<T0, T1, T2, T3> extends Tuple {
+
+  private static final long serialVersionUID = 1L;
+
+  /** Field 0 of the tuple. */
+  public T0 t1;
+  /** Field 1 of the tuple. */
+  public T1 t2;
+  /** Field 2 of the tuple. */
+  public T2 t3;
+  /** Field 3 of the tuple. */
+  public T3 t4;
+
+  /** Creates a new tuple where all fields are null. */
+  public Tuple4() {}
+
+  /**
+   * Creates a new tuple and assigns the given values to the tuple's fields.
+   *
+   * @param t0 The value for field 0
+   * @param t1 The value for field 1
+   * @param t2 The value for field 2
+   * @param t4 The value for field 3
+   */
+  public Tuple4(T0 t0, T1 t1, T2 t2, T3 t4) {
+    this.t1 = t0;
+    this.t2 = t1;
+    this.t3 = t2;
+    this.t4 = t4;
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T> T get(int pos) {
+    switch (pos) {
+      case 0:
+        return (T) this.t1;
+      case 1:
+        return (T) this.t2;
+      case 2:
+        return (T) this.t3;
+      case 3:
+        return (T) this.t4;
+      default:
+        throw new IndexOutOfBoundsException(String.valueOf(pos));
+    }
+  }
+
+  @Override
+  @SuppressWarnings("unchecked")
+  public <T> void set(T value, int pos) {
+    switch (pos) {
+      case 0:
+        this.t1 = (T0) value;
+        break;
+      case 1:
+        this.t2 = (T1) value;
+        break;
+      case 2:
+        this.t3 = (T2) value;
+        break;
+      case 3:
+        this.t4 = (T3) value;
+        break;
+      default:
+        throw new IndexOutOfBoundsException(String.valueOf(pos));
+    }
+  }
+
+  /**
+   * Sets new values to all fields of the tuple.
+   *
+   * @param f0 The value for field 0
+   * @param f1 The value for field 1
+   * @param f2 The value for field 2
+   * @param f3 The value for field 3
+   */
+  public void set(T0 f0, T1 f1, T2 f2, T3 f3) {
+    this.t1 = f0;
+    this.t2 = f1;
+    this.t3 = f2;
+    this.t4 = f3;
+  }
+
+  /**
+   * Deep equality for tuples by calling equals() on the tuple members.
+   *
+   * @param o the object checked for equality
+   * @return true if this is equal to o.
+   */
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (!(o instanceof Tuple4)) {
+      return false;
+    }
+    @SuppressWarnings("rawtypes")
+    Tuple4 tuple = (Tuple4) o;
+    if (!Objects.equals(t1, tuple.t1)) {
+      return false;
+    }
+    if (!Objects.equals(t2, tuple.t2)) {
+      return false;
+    }
+    if (!Objects.equals(t3, tuple.t3)) {
+      return false;
+    }
+    return Objects.equals(t4, tuple.t4);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = t1 != null ? t1.hashCode() : 0;
+    result = 31 * result + (t2 != null ? t2.hashCode() : 0);
+    result = 31 * result + (t3 != null ? t3.hashCode() : 0);
+    result = 31 * result + (t4 != null ? t4.hashCode() : 0);
+    return result;
+  }
+
+  /**
+   * Shallow tuple copy.
+   *
+   * @return A new Tuple with the same fields as this.
+   */
+  @Override
+  @SuppressWarnings("unchecked")
+  public Tuple4<T0, T1, T2, T3> copy() {
+    return new Tuple4<>(this.t1, this.t2, this.t3, this.t4);
+  }
+
+  /**
+   * Creates a new tuple and assigns the given values to the tuple's fields. 
This is more convenient
+   * than using the constructor, because the compiler can infer the generic 
type arguments
+   * implicitly. For example: {@code Tuple3.of(n, x, s)} instead of {@code new 
Tuple3<Integer,
+   * Double, String>(n, x, s)}
+   */
+  public static <T0, T1, T2, T3> Tuple4<T0, T1, T2, T3> of(T0 f0, T1 f1, T2 
f2, T3 f3) {
+    return new Tuple4<>(f0, f1, f2, f3);
+  }
+}
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/annotation/ApiAccess.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/annotation/OpenAPI.java
similarity index 77%
copy from 
streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/annotation/ApiAccess.java
copy to 
streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/annotation/OpenAPI.java
index 0ef500cff..02f072a1c 100644
--- 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/annotation/ApiAccess.java
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/annotation/OpenAPI.java
@@ -24,4 +24,26 @@ import java.lang.annotation.Target;
 
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.RUNTIME)
-public @interface ApiAccess {}
+public @interface OpenAPI {
+
+  String name() default "";
+
+  Param[] header() default {};
+
+  Param[] param() default {};
+
+  @interface Param {
+
+    String name();
+
+    String description();
+
+    boolean required();
+
+    Class<?> type();
+
+    String defaultValue() default "";
+
+    String bindFor() default "";
+  }
+}
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/aspect/StreamParkAspect.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/aspect/StreamParkAspect.java
index 352362e66..82c9cc484 100644
--- 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/aspect/StreamParkAspect.java
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/aspect/StreamParkAspect.java
@@ -19,7 +19,7 @@ package org.apache.streampark.console.core.aspect;
 
 import org.apache.streampark.console.base.domain.RestResponse;
 import org.apache.streampark.console.base.exception.ApiAlertException;
-import org.apache.streampark.console.core.annotation.ApiAccess;
+import org.apache.streampark.console.core.annotation.OpenAPI;
 import org.apache.streampark.console.core.annotation.PermissionScope;
 import org.apache.streampark.console.core.entity.Application;
 import org.apache.streampark.console.core.enums.UserType;
@@ -72,8 +72,8 @@ public class StreamParkAspect {
     Boolean isApi =
         (Boolean) 
SecurityUtils.getSubject().getSession().getAttribute(AccessToken.IS_API_TOKEN);
     if (isApi != null && isApi) {
-      ApiAccess apiAccess = 
methodSignature.getMethod().getAnnotation(ApiAccess.class);
-      if (apiAccess == null) {
+      OpenAPI openAPI = 
methodSignature.getMethod().getAnnotation(OpenAPI.class);
+      if (openAPI == null) {
         throw new ApiAlertException("current api unsupported!");
       }
     }
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/annotation/ApiAccess.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/bean/OpenAPISchema.java
similarity index 58%
rename from 
streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/annotation/ApiAccess.java
rename to 
streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/bean/OpenAPISchema.java
index 0ef500cff..504724bd0 100644
--- 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/annotation/ApiAccess.java
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/bean/OpenAPISchema.java
@@ -15,13 +15,40 @@
  * limitations under the License.
  */
 
-package org.apache.streampark.console.core.annotation;
+package org.apache.streampark.console.core.bean;
 
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.Getter;
+import lombok.Setter;
 
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ApiAccess {}
+import java.util.List;
+
+@Getter
+@Setter
+public class OpenAPISchema {
+
+  private String url;
+
+  private String method;
+
+  private List<Schema> header;
+
+  private List<Schema> schema;
+
+  @Getter
+  @Setter
+  public static class Schema {
+
+    private String name;
+
+    private String type;
+
+    private boolean required;
+
+    private String description;
+
+    private String defaultValue;
+
+    @JsonIgnore private String bindFor;
+  }
+}
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/component/OpenAPIComponent.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/component/OpenAPIComponent.java
new file mode 100644
index 000000000..038b69109
--- /dev/null
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/component/OpenAPIComponent.java
@@ -0,0 +1,218 @@
+/*
+ * 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.streampark.console.core.component;
+
+import org.apache.streampark.common.util.CURLBuilder;
+import org.apache.streampark.common.util.ReflectUtils;
+import org.apache.streampark.console.base.util.Tuple2;
+import org.apache.streampark.console.core.annotation.OpenAPI;
+import org.apache.streampark.console.core.bean.OpenAPISchema;
+import org.apache.streampark.console.core.controller.OpenAPIController;
+import org.apache.streampark.console.core.service.ServiceHelper;
+import org.apache.streampark.console.system.service.AccessTokenService;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpMethod;
+import org.springframework.stereotype.Component;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PatchMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Component
+public class OpenAPIComponent {
+
+  @Autowired private AccessTokenService accessTokenService;
+
+  @Autowired private ServiceHelper serviceHelper;
+
+  private final Map<String, String> types = new HashMap<>();
+
+  private final Map<String, OpenAPISchema> schemas = new HashMap<>();
+
+  public synchronized OpenAPISchema getOpenAPISchema(String name) {
+    if (schemas.isEmpty()) {
+      try {
+        initOpenAPISchema();
+      } catch (Exception e) {
+        log.error("InitOpenAPISchema failed", e);
+      }
+    }
+    return schemas.get(name);
+  }
+
+  public String getOpenApiCUrl(String baseUrl, Long appId, Long teamId, String 
name) {
+    OpenAPISchema schema = this.getOpenAPISchema(name);
+    if (schema == null) {
+      throw new UnsupportedOperationException("Unsupported OpenAPI: " + name);
+    }
+
+    String url = schema.getUrl();
+    if (StringUtils.isNoneBlank(baseUrl)) {
+      url = baseUrl + url;
+    }
+    CURLBuilder curlBuilder = new CURLBuilder(url);
+    curlBuilder
+        .addHeader("Content-Type", "application/x-www-form-urlencoded; 
charset=UTF-8")
+        .addHeader(
+            "Authorization", 
accessTokenService.getByUserId(serviceHelper.getUserId()).getToken());
+
+    schema
+        .getSchema()
+        .forEach(
+            c -> {
+              if (c.isRequired()) {
+                if ("appId".equals(c.getBindFor())) {
+                  curlBuilder.addFormData(c.getName(), appId);
+                } else if ("teamId".equals(c.getBindFor())) {
+                  curlBuilder.addFormData(c.getName(), teamId);
+                }
+              } else {
+                curlBuilder.addFormData(c.getName(), c.getDefaultValue());
+              }
+            });
+    return curlBuilder.build();
+  }
+
+  private void initOpenAPISchema() {
+    initTypes();
+    Class<?> clazz = OpenAPIController.class;
+    RequestMapping requestMapping = 
clazz.getDeclaredAnnotation(RequestMapping.class);
+    String basePath = requestMapping.value()[0];
+    List<Method> methodList = ReflectUtils.getMethodsByAnnotation(clazz, 
OpenAPI.class);
+
+    for (Method method : methodList) {
+      OpenAPISchema detail = new OpenAPISchema();
+
+      List<OpenAPISchema.Schema> headerList = new ArrayList<>();
+      OpenAPI openAPI = method.getDeclaredAnnotation(OpenAPI.class);
+      for (OpenAPI.Param header : openAPI.header()) {
+        headerList.add(paramToSchema(header));
+      }
+
+      List<OpenAPISchema.Schema> paramList = new ArrayList<>();
+      for (OpenAPI.Param param : openAPI.param()) {
+        paramList.add(paramToSchema(param));
+      }
+
+      detail.setSchema(paramList);
+      detail.setHeader(headerList);
+
+      Tuple2<String, String[]> methodURI = getMethodAndRequestURI(method);
+      String[] requestURI = methodURI.t2;
+      String uri = (requestURI != null && requestURI.length > 0) ? 
requestURI[0] : "";
+      String restURI = "/" + basePath;
+      if (uri != null) {
+        restURI += "/" + uri;
+      }
+      restURI = restURI.replaceAll("/+", "/").replaceAll("/$", "");
+      detail.setUrl(restURI);
+      detail.setMethod(methodURI.t1);
+      schemas.put(openAPI.name(), detail);
+    }
+  }
+
+  private OpenAPISchema.Schema paramToSchema(OpenAPI.Param param) {
+    OpenAPISchema.Schema schema = new OpenAPISchema.Schema();
+    schema.setName(param.name());
+    if (StringUtils.isBlank(param.bindFor())) {
+      schema.setBindFor(param.name());
+    } else {
+      schema.setBindFor(param.bindFor());
+    }
+    schema.setRequired(param.required());
+    schema.setDescription(param.description());
+    schema.setDefaultValue(param.defaultValue());
+    String type = types.get(param.type().getSimpleName());
+    if (type != null) {
+      schema.setType(type);
+    } else {
+      schema.setType("string(" + param.type().getSimpleName() + ")");
+    }
+    return schema;
+  }
+
+  private Tuple2<String, String[]> getMethodAndRequestURI(Method method) {
+    method.setAccessible(true);
+
+    GetMapping getMapping = method.getDeclaredAnnotation(GetMapping.class);
+    if (getMapping != null) {
+      return Tuple2.of(HttpMethod.GET.name(), getMapping.value());
+    }
+
+    PostMapping postMapping = method.getDeclaredAnnotation(PostMapping.class);
+    if (postMapping != null) {
+      return Tuple2.of(HttpMethod.POST.name(), postMapping.value());
+    }
+
+    DeleteMapping deleteMapping = 
method.getDeclaredAnnotation(DeleteMapping.class);
+    if (deleteMapping != null) {
+      return Tuple2.of(HttpMethod.DELETE.name(), deleteMapping.value());
+    }
+
+    PatchMapping patchMapping = 
method.getDeclaredAnnotation(PatchMapping.class);
+    if (patchMapping != null) {
+      return Tuple2.of(HttpMethod.PATCH.name(), patchMapping.value());
+    }
+
+    PutMapping putMapping = method.getDeclaredAnnotation(PutMapping.class);
+    if (putMapping != null) {
+      return Tuple2.of(HttpMethod.PUT.name(), putMapping.value());
+    }
+
+    throw new IllegalArgumentException(
+        "get http method and requestURI failed: " + method.getName());
+  }
+
+  private void initTypes() {
+    types.put("String", "string");
+
+    types.put("int", "integer(int32)");
+    types.put("Integer", "integer(int32)");
+    types.put("Short", "integer(int32)");
+
+    types.put("long", "integer(int64)");
+    types.put("Long", "integer(int64)");
+
+    types.put("double", "number(double)");
+    types.put("Double", "number(double)");
+
+    types.put("float", "number(float)");
+    types.put("Float", "number(float)");
+    types.put("boolean", "boolean");
+    types.put("Boolean", "boolean");
+
+    types.put("byte", "string(byte)");
+    types.put("Byte", "string(byte)");
+
+    types.put("Date", "string(date)");
+    types.put("DateTime", "string(datetime)");
+  }
+}
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/controller/ApplicationController.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/controller/ApplicationController.java
index b2e6ace9e..411473a3f 100644
--- 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/controller/ApplicationController.java
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/controller/ApplicationController.java
@@ -22,7 +22,6 @@ import org.apache.streampark.common.util.YarnUtils;
 import org.apache.streampark.console.base.domain.RestRequest;
 import org.apache.streampark.console.base.domain.RestResponse;
 import org.apache.streampark.console.base.exception.InternalException;
-import org.apache.streampark.console.core.annotation.ApiAccess;
 import org.apache.streampark.console.core.annotation.AppUpdated;
 import org.apache.streampark.console.core.annotation.PermissionScope;
 import org.apache.streampark.console.core.entity.Application;
@@ -141,7 +140,6 @@ public class ApplicationController {
     return RestResponse.success(stateEnum.get());
   }
 
-  @ApiAccess
   @PermissionScope(app = "#app.id", team = "#app.teamId")
   @PostMapping(value = "start")
   @RequiresPermissions("app:start")
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/controller/OpenAPIController.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/controller/OpenAPIController.java
new file mode 100644
index 000000000..613d8c379
--- /dev/null
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/controller/OpenAPIController.java
@@ -0,0 +1,156 @@
+/*
+ * 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.streampark.console.core.controller;
+
+import org.apache.streampark.console.base.domain.RestResponse;
+import org.apache.streampark.console.core.annotation.OpenAPI;
+import org.apache.streampark.console.core.annotation.PermissionScope;
+import org.apache.streampark.console.core.bean.OpenAPISchema;
+import org.apache.streampark.console.core.component.OpenAPIComponent;
+import org.apache.streampark.console.core.entity.Application;
+import org.apache.streampark.console.core.service.ApplicationService;
+
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.validation.constraints.NotBlank;
+
+@Validated
+@RestController
+@RequestMapping("openapi")
+public class OpenAPIController {
+
+  @Autowired private OpenAPIComponent openAPIComponent;
+
+  @Autowired private ApplicationService applicationService;
+
+  @OpenAPI(
+      name = "flinkStart",
+      header = {
+        @OpenAPI.Param(
+            name = "Authorization",
+            description = "Access authorization token",
+            required = true,
+            type = String.class)
+      },
+      param = {
+        @OpenAPI.Param(
+            name = "id",
+            description = "current flink application id",
+            required = true,
+            type = Long.class,
+            bindFor = "appId"),
+        @OpenAPI.Param(
+            name = "teamId",
+            description = "current user teamId",
+            required = true,
+            type = Long.class),
+        @OpenAPI.Param(
+            name = "savePointed",
+            description = "restored app from the savepoint or latest 
checkpoint",
+            required = false,
+            type = String.class,
+            defaultValue = "false"),
+        @OpenAPI.Param(
+            name = "savePoint",
+            description = "savepoint or checkpoint path",
+            required = false,
+            type = String.class),
+        @OpenAPI.Param(
+            name = "allowNonRestored",
+            description = "ignore savepoint if cannot be restored",
+            required = false,
+            type = boolean.class,
+            defaultValue = "false")
+      })
+  @PermissionScope(app = "#app.appId", team = "#app.teamId")
+  @PostMapping("app/start")
+  @RequiresPermissions("app:start")
+  public RestResponse flinkStart(Application app) throws Exception {
+    applicationService.start(app, false);
+    return RestResponse.success(true);
+  }
+
+  @OpenAPI(
+      name = "flinkCancel",
+      header = {
+        @OpenAPI.Param(
+            name = "Authorization",
+            description = "Access authorization token",
+            required = true,
+            type = String.class)
+      },
+      param = {
+        @OpenAPI.Param(
+            name = "id",
+            description = "current flink application id",
+            required = true,
+            type = Long.class,
+            bindFor = "appId"),
+        @OpenAPI.Param(
+            name = "teamId",
+            description = "current user teamId",
+            required = true,
+            type = Long.class),
+        @OpenAPI.Param(
+            name = "savePointed",
+            description = "trigger savepoint before taking stopping",
+            required = false,
+            type = boolean.class,
+            defaultValue = "false"),
+        @OpenAPI.Param(
+            name = "savePoint",
+            description = "savepoint path",
+            required = false,
+            type = String.class),
+        @OpenAPI.Param(
+            name = "drain",
+            description = "send max watermark before canceling",
+            required = false,
+            type = boolean.class,
+            defaultValue = "false"),
+      })
+  @PermissionScope(app = "#app.appId", team = "#app.teamId")
+  @PostMapping("app/cancel")
+  @RequiresPermissions("app:cancel")
+  public RestResponse flinkCancel(Application app) throws Exception {
+    applicationService.cancel(app);
+    return RestResponse.success();
+  }
+
+  @PostMapping("curl")
+  public RestResponse copyOpenApiCurl(
+      String baseUrl,
+      Long appId,
+      @NotBlank(message = "{required}") Long teamId,
+      @NotBlank(message = "{required}") String name) {
+    String url = openAPIComponent.getOpenApiCUrl(baseUrl, appId, teamId, name);
+    return RestResponse.success(url);
+  }
+
+  @PostMapping("schema")
+  public RestResponse schema(@NotBlank(message = "{required}") String name) {
+    OpenAPISchema openAPISchema = openAPIComponent.getOpenAPISchema(name);
+    return RestResponse.success(openAPISchema);
+  }
+}

Reply via email to