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

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


The following commit(s) were added to refs/heads/dev-2.1.7 by this push:
     new 6ccae3149 [Improve] maven args check improvement
6ccae3149 is described below

commit 6ccae3149c83c8311b2c884142697cc8cc1054d8
Author: benjobs <[email protected]>
AuthorDate: Sun Oct 26 23:21:01 2025 +0800

    [Improve] maven args check improvement
---
 .../streampark/console/base/util/MavenUtils.java   | 696 ++++++++++++++++
 .../streampark/console/core/entity/Project.java    | 131 +--
 .../console/core/task/ProjectBuildTask.java        | 888 +++++++++++++++++----
 3 files changed, 1466 insertions(+), 249 deletions(-)

diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/MavenUtils.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/MavenUtils.java
new file mode 100644
index 000000000..6001f07aa
--- /dev/null
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/base/util/MavenUtils.java
@@ -0,0 +1,696 @@
+package org.apache.streampark.console.base.util;
+
+import org.apache.streampark.common.conf.CommonConfig;
+import org.apache.streampark.common.conf.InternalConfigHolder;
+import org.apache.streampark.common.util.Utils;
+
+import org.apache.commons.lang3.StringUtils;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.File;
+import java.nio.file.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.Normalizer;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Slf4j
+public class MavenUtils {
+
+  // Security constants for Maven parameter validation
+  private static final int MAX_BUILD_ARGS_LENGTH = 2048;
+  private static final int MAX_MAVEN_ARG_LENGTH = 512;
+  private static final Pattern COMMAND_INJECTION_PATTERN =
+      Pattern.compile(
+          "(`[^`]*`)|"
+              + // Backticks
+              "(\\$\\([^)]*\\))|"
+              + // $() command substitution
+              "(\\$\\{[^}]*})|"
+              + // ${} variable substitution
+              "([;&|><])|"
+              + // Command separators and redirects
+              "(\\\\[nrt])|"
+              + // Escaped newlines/tabs
+              "([\\r\\n\\t])|"
+              + // Actual control characters
+              "(\\\\x[0-9a-fA-F]{2})|"
+              + // Hex encoded characters
+              "(%[0-9a-fA-F]{2})|"
+              + // URL encoded characters
+              "(\\\\u[0-9a-fA-F]{4})", // Unicode encoded characters
+          Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
+
+  // Whitelist of allowed Maven arguments
+  private static final Set<String> ALLOWED_MAVEN_ARGS =
+      Collections.unmodifiableSet(
+          new HashSet<>(
+              Arrays.asList(
+                  "-D",
+                  "--define",
+                  "-P",
+                  "--activate-profiles",
+                  "-q",
+                  "--quiet",
+                  "-X",
+                  "--debug",
+                  "-e",
+                  "--errors",
+                  "-f",
+                  "--file",
+                  "-s",
+                  "--settings",
+                  "-t",
+                  "--toolchains",
+                  "-T",
+                  "--threads",
+                  "-B",
+                  "--batch-mode",
+                  "-V",
+                  "--show-version",
+                  "-U",
+                  "--update-snapshots",
+                  "-N",
+                  "--non-recursive",
+                  "-C",
+                  "--strict-checksums",
+                  "-c",
+                  "--lax-checksums",
+                  "-o",
+                  "--offline",
+                  "--no-snapshot-updates",
+                  "--fail-at-end",
+                  "--fail-fast",
+                  "--fail-never",
+                  "--resume-from",
+                  "--projects",
+                  "--also-make",
+                  "--also-make-dependents",
+                  "clean",
+                  "compile",
+                  "test",
+                  "package",
+                  "install",
+                  "deploy",
+                  "validate",
+                  "initialize",
+                  "generate-sources",
+                  "process-sources",
+                  "generate-resources",
+                  "process-resources",
+                  "process-classes",
+                  "generate-test-sources",
+                  "process-test-sources",
+                  "generate-test-resources",
+                  "process-test-resources",
+                  "test-compile",
+                  "process-test-classes",
+                  "prepare-package",
+                  "pre-integration-test",
+                  "integration-test",
+                  "post-integration-test",
+                  "verify",
+                  "install",
+                  "deploy")));
+
+  // Allowed system properties (commonly used in Maven builds)
+  private static final Set<String> ALLOWED_SYSTEM_PROPERTIES =
+      Collections.unmodifiableSet(
+          new HashSet<>(
+              Arrays.asList(
+                  "skipTests",
+                  "maven.test.skip",
+                  "maven.javadoc.skip",
+                  "maven.source.skip",
+                  "project.build.sourceEncoding",
+                  "project.reporting.outputEncoding",
+                  "maven.compiler.source",
+                  "maven.compiler.target",
+                  "maven.compiler.release",
+                  "flink.version",
+                  "scala.version",
+                  "hadoop.version",
+                  "kafka.version",
+                  "java.version",
+                  "encoding",
+                  "file.encoding")));
+
+  @JsonIgnore
+  public static String getMavenArgs(String buildArgs) {
+    try {
+      StringBuilder mvnArgBuffer = new StringBuilder(" clean package 
-DskipTests ");
+
+      // Apply security validation to build arguments
+      if (StringUtils.isNotBlank(buildArgs)) {
+        String validatedBuildArgs = 
validateAndSanitizeBuildArgs(buildArgs.trim());
+        if (StringUtils.isNotBlank(validatedBuildArgs)) {
+          mvnArgBuffer.append(validatedBuildArgs);
+        }
+      }
+
+      // Enhanced --settings validation with path security
+      String setting = 
InternalConfigHolder.get(CommonConfig.MAVEN_SETTINGS_PATH());
+      if (StringUtils.isNotBlank(setting)) {
+        String validatedSettingsPath = 
validateAndSanitizeSettingsPath(setting);
+        mvnArgBuffer.append(" --settings ").append(validatedSettingsPath);
+      }
+
+      // Get final Maven arguments string
+      String mvnArgs = mvnArgBuffer.toString();
+
+      // Enhanced security checks
+      validateFinalMavenCommand(mvnArgs);
+
+      // Find and validate Maven executable with enhanced security
+      String mvn = getSecureMavenExecutable();
+
+      log.info(
+          "🔧 Secure Maven command prepared: {} {}",
+          sanitizeForLogging(mvn),
+          sanitizeForLogging(mvnArgs));
+      return mvn.concat(mvnArgs);
+
+    } catch (SecurityException e) {
+      log.error("🚨 Security validation failed for Maven build: {}", 
e.getMessage());
+      throw new IllegalArgumentException(
+          "Maven build security validation failed: " + e.getMessage(), e);
+    } catch (Exception e) {
+      log.error("❌ Failed to prepare Maven command: {}", e.getMessage());
+      throw new IllegalArgumentException("Failed to prepare Maven command: " + 
e.getMessage(), e);
+    }
+  }
+
+  /**
+   * Validates and sanitizes Maven settings file path to prevent path 
traversal attacks.
+   *
+   * @param settingsPath the Maven settings file path
+   * @return validated and normalized settings path
+   * @throws SecurityException if path validation fails
+   */
+  private static String validateAndSanitizeSettingsPath(String settingsPath) {
+    if (StringUtils.isBlank(settingsPath)) {
+      logSecurityEvent("ERROR", "PATH_VALIDATION", "Settings path is blank", 
"");
+      throw new SecurityException("Settings path cannot be blank");
+    }
+
+    logSecurityEvent(
+        "INFO", "PATH_VALIDATION", "Starting Maven settings path validation", 
settingsPath);
+
+    try {
+      // Normalize the path to resolve any relative components
+      Path normalizedPath = Paths.get(settingsPath).normalize();
+      String pathString = normalizedPath.toString();
+
+      // Security checks for path traversal
+      if (pathString.contains("..") || pathString.contains("~")) {
+        logSecurityEvent(
+            "VIOLATION",
+            "PATH_TRAVERSAL_DETECTION",
+            "Path traversal attempt detected in settings path",
+            settingsPath);
+        throw new SecurityException("Path traversal detected in settings 
path");
+      }
+
+      // Verify the file exists and is readable
+      File file = normalizedPath.toFile();
+      if (!file.exists()) {
+        throw new SecurityException(
+            String.format(
+                "Maven settings file does not exist: %s", 
sanitizeForLogging(pathString)));
+      }
+
+      if (!file.isFile()) {
+        throw new SecurityException(
+            String.format("Maven settings path is not a file: %s", 
sanitizeForLogging(pathString)));
+      }
+
+      if (!file.canRead()) {
+        throw new SecurityException(
+            String.format(
+                "Maven settings file is not readable: %s", 
sanitizeForLogging(pathString)));
+      }
+
+      // Additional security: check file size to prevent DoS
+      if (file.length() > 10 * 1024 * 1024) { // 10MB limit
+        throw new SecurityException("Maven settings file is too large 
(>10MB)");
+      }
+
+      logSecurityEvent(
+          "SUCCESS", "PATH_VALIDATION", "Maven settings path validation 
completed", pathString);
+      return pathString;
+
+    } catch (InvalidPathException e) {
+      logSecurityEvent(
+          "VIOLATION", "PATH_SYNTAX_ERROR", "Invalid path syntax in settings 
path", settingsPath);
+      throw new SecurityException("Invalid path syntax in settings path", e);
+    }
+  }
+
+  /**
+   * Performs final validation on the complete Maven command string.
+   *
+   * @param mvnArgs the complete Maven arguments string
+   * @throws SecurityException if validation fails
+   */
+  private static void validateFinalMavenCommand(String mvnArgs) {
+    logSecurityEvent(
+        "INFO", "FINAL_COMMAND_VALIDATION", "Starting final Maven command 
validation", mvnArgs);
+
+    // Check for newline characters (existing validation)
+    if (mvnArgs.contains("\n") || mvnArgs.contains("\r")) {
+      logSecurityEvent(
+          "VIOLATION",
+          "CONTROL_CHARACTER_DETECTION",
+          "Control characters detected in maven command",
+          mvnArgs);
+      throw new SecurityException("Control characters detected in maven build 
parameters");
+    }
+
+    // Additional validation using enhanced pattern matching
+    Matcher dangerousMatcher = COMMAND_INJECTION_PATTERN.matcher(mvnArgs);
+    if (dangerousMatcher.find()) {
+      String dangerousPattern = dangerousMatcher.group();
+      logSecurityEvent(
+          "VIOLATION",
+          "INJECTION_DETECTION",
+          "Command injection pattern in final Maven command",
+          dangerousPattern);
+      throw new SecurityException("Command injection pattern detected in final 
Maven command");
+    }
+
+    // Validate total command length
+    if (mvnArgs.length() > MAX_BUILD_ARGS_LENGTH + 100) { // Allow some buffer 
for default args
+      logSecurityEvent(
+          "VIOLATION",
+          "COMMAND_LENGTH_EXCEEDED",
+          String.format("Maven command exceeds maximum length: %d", 
mvnArgs.length()),
+          mvnArgs);
+      throw new SecurityException("Maven command exceeds maximum allowed 
length");
+    }
+
+    logSecurityEvent(
+        "SUCCESS", "FINAL_COMMAND_VALIDATION", "Final Maven command validation 
completed", mvnArgs);
+  }
+
+  /**
+   * Securely determines the Maven executable with enhanced validation.
+   *
+   * @return validated Maven executable path
+   * @throws SecurityException if Maven executable validation fails
+   */
+  private static String getSecureMavenExecutable() {
+    boolean windows = Utils.isWindows();
+    String mvn = windows ? "mvn.cmd" : "mvn";
+
+    // Validate environment variables for potential injection
+    String mavenHome = validateMavenHomeEnvironment();
+
+    boolean useWrapper = true;
+    if (mavenHome != null) {
+      mvn = mavenHome + "/bin/" + mvn;
+      try {
+        // Test Maven installation with security considerations
+        ProcessBuilder pb = new ProcessBuilder(mvn, "--version");
+        pb.environment().clear(); // Clear environment to prevent injection
+        Process process = pb.start();
+        boolean finished = process.waitFor(10, TimeUnit.SECONDS);
+
+        if (finished && process.exitValue() == 0) {
+          useWrapper = false;
+          log.info("✅ Validated system Maven installation: {}", 
sanitizeForLogging(mvn));
+        } else {
+          log.warn("âš ī¸  System Maven validation failed, using wrapper");
+        }
+      } catch (Exception e) {
+        log.warn("âš ī¸  Maven validation error: {}, using wrapper", 
e.getMessage());
+      }
+    }
+
+    if (useWrapper) {
+      String wrapperPath = WebUtils.getAppHome().concat(windows ? 
"/bin/mvnw.cmd" : "/bin/mvnw");
+
+      // Validate wrapper executable exists and is secure
+      File wrapperFile = new File(wrapperPath);
+      if (!wrapperFile.exists() || !wrapperFile.canExecute()) {
+        throw new SecurityException("Maven wrapper not found or not 
executable: " + wrapperPath);
+      }
+
+      mvn = wrapperPath;
+      log.info("✅ Using secure Maven wrapper: {}", sanitizeForLogging(mvn));
+    }
+
+    return mvn;
+  }
+
+  /**
+   * Validates Maven home environment variables for security issues.
+   *
+   * @return validated Maven home path or null if not set/invalid
+   */
+  private static String validateMavenHomeEnvironment() {
+    String mavenHome = System.getenv("M2_HOME");
+    if (mavenHome == null) {
+      mavenHome = System.getenv("MAVEN_HOME");
+    }
+
+    if (mavenHome == null) {
+      return null;
+    }
+
+    try {
+      // Validate and normalize the Maven home path
+      Path normalizedPath = Paths.get(mavenHome).normalize();
+      String pathString = normalizedPath.toString();
+
+      // Security checks
+      if (pathString.contains("..") || pathString.contains("~")) {
+        log.warn("âš ī¸  Suspicious Maven home path, ignoring: {}", 
sanitizeForLogging(mavenHome));
+        return null;
+      }
+
+      File mavenDir = normalizedPath.toFile();
+      if (!mavenDir.exists() || !mavenDir.isDirectory()) {
+        log.warn("âš ī¸  Invalid Maven home directory, ignoring: {}", 
sanitizeForLogging(pathString));
+        return null;
+      }
+
+      return pathString;
+
+    } catch (InvalidPathException e) {
+      log.warn("âš ī¸  Invalid Maven home path syntax, ignoring: {}", 
sanitizeForLogging(mavenHome));
+      return null;
+    }
+  }
+
+  /**
+   * Logs security events for auditing and monitoring purposes. This method 
provides comprehensive
+   * security logging for Maven build operations.
+   *
+   * @param eventType the type of security event (SUCCESS, WARNING, VIOLATION, 
etc.)
+   * @param operation the operation being performed (BUILD_VALIDATION, 
PATH_VALIDATION, etc.)
+   * @param details additional details about the event
+   * @param sensitiveData any sensitive data that should be sanitized for 
logging
+   */
+  private static void logSecurityEvent(
+      String eventType, String operation, String details, String 
sensitiveData) {
+    String sanitizedData = sanitizeForLogging(sensitiveData);
+
+    switch (eventType.toUpperCase()) {
+      case "SUCCESS":
+        log.info(
+            "✅ [SECURITY-AUDIT] {} - {}: {} | Data: {}",
+            eventType,
+            operation,
+            details,
+            sanitizedData);
+        break;
+      case "WARNING":
+        log.warn(
+            "âš ī¸  [SECURITY-AUDIT] {} - {}: {} | Data: {}",
+            eventType,
+            operation,
+            details,
+            sanitizedData);
+        break;
+      case "VIOLATION":
+      case "ERROR":
+        log.error(
+            "🚨 [SECURITY-AUDIT] {} - {}: {} | Data: {}",
+            eventType,
+            operation,
+            details,
+            sanitizedData);
+        break;
+      default:
+        log.info(
+            "â„šī¸  [SECURITY-AUDIT] {} - {}: {} | Data: {}",
+            eventType,
+            operation,
+            details,
+            sanitizedData);
+    }
+
+    // Additional structured logging for security monitoring systems
+    log.info(
+        "SECURITY_EVENT_JSON: 
{{\"timestamp\":\"{}\",\"event_type\":\"{}\",\"operation\":\"{}\",\"details\":\"{}\",\"source\":\"Project.getMavenArgs\"}}",
+        Instant.now().toString(),
+        eventType,
+        operation,
+        details.replace("\"", "\\\""));
+  }
+
+  /**
+   * Comprehensive security validation for Maven build arguments. Implements 
multiple layers of
+   * security checks to prevent command injection attacks.
+   *
+   * @param buildArgs the raw build arguments string
+   * @return sanitized and validated build arguments
+   * @throws SecurityException if dangerous patterns or parameters are detected
+   */
+  private static String validateAndSanitizeBuildArgs(String buildArgs) {
+    if (StringUtils.isBlank(buildArgs)) {
+      logSecurityEvent("SUCCESS", "BUILD_VALIDATION", "Empty build arguments 
processed", "");
+      return "";
+    }
+
+    logSecurityEvent(
+        "INFO", "BUILD_VALIDATION", "Starting validation of build arguments", 
buildArgs);
+
+    // 1. Length validation to prevent DoS attacks
+    if (buildArgs.length() > MAX_BUILD_ARGS_LENGTH) {
+      logSecurityEvent(
+          "VIOLATION",
+          "LENGTH_VALIDATION",
+          String.format(
+              "Build arguments exceed maximum length. Length: %d, Limit: %d",
+              buildArgs.length(), MAX_BUILD_ARGS_LENGTH),
+          buildArgs);
+      throw new SecurityException(
+          "Build arguments exceed maximum allowed length: " + 
MAX_BUILD_ARGS_LENGTH);
+    }
+
+    // 2. Normalize Unicode and decode potential encoding attacks
+    String normalizedArgs = normalizeAndDecode(buildArgs);
+    if (!normalizedArgs.equals(buildArgs)) {
+      logSecurityEvent(
+          "WARNING",
+          "ENCODING_DETECTION",
+          "Encoding or Unicode normalization applied to build arguments",
+          String.format("Original: %s | Normalized: %s", buildArgs, 
normalizedArgs));
+    }
+
+    // 3. Advanced command injection pattern detection
+    Matcher dangerousMatcher = 
COMMAND_INJECTION_PATTERN.matcher(normalizedArgs);
+    if (dangerousMatcher.find()) {
+      String dangerousPattern = dangerousMatcher.group();
+      logSecurityEvent(
+          "VIOLATION",
+          "INJECTION_DETECTION",
+          "Command injection pattern detected in build arguments",
+          String.format("Pattern: %s | Full args: %s", dangerousPattern, 
normalizedArgs));
+      throw new SecurityException(
+          "Dangerous command injection pattern detected: " + 
sanitizeForLogging(dangerousPattern));
+    }
+
+    // 4. Whitelist-based argument validation
+    String validatedArgs = validateArgumentsAgainstWhitelist(normalizedArgs);
+    logSecurityEvent(
+        "SUCCESS",
+        "BUILD_VALIDATION",
+        "Build arguments validation completed successfully",
+        validatedArgs);
+    return validatedArgs;
+  }
+
+  /**
+   * Normalizes Unicode characters and decodes potential encoding attacks.
+   *
+   * @param input the input string to normalize
+   * @return normalized string
+   */
+  private static String normalizeAndDecode(String input) {
+    // Normalize Unicode characters to prevent Unicode-based attacks
+    String normalized = Normalizer.normalize(input, Normalizer.Form.NFC);
+
+    // Basic URL decode to catch simple encoding attempts
+    normalized =
+        normalized
+            .replace("%20", " ")
+            .replace("%3B", ";")
+            .replace("%7C", "|")
+            .replace("%26", "&")
+            .replace("%3E", ">")
+            .replace("%3C", "<")
+            .replace("%24", "$")
+            .replace("%60", "`");
+
+    return normalized;
+  }
+
+  /**
+   * Validates Maven arguments against predefined whitelists.
+   *
+   * @param args the normalized arguments string
+   * @return validated arguments string
+   * @throws SecurityException if invalid arguments are found
+   */
+  private static String validateArgumentsAgainstWhitelist(String args) {
+    String[] argArray = args.trim().split("\\s+");
+    StringBuilder validatedArgs = new StringBuilder();
+
+    for (int i = 0; i < argArray.length; i++) {
+      String arg = argArray[i].trim();
+
+      if (StringUtils.isBlank(arg)) {
+        continue;
+      }
+
+      // Validate individual argument length
+      if (arg.length() > MAX_MAVEN_ARG_LENGTH) {
+        log.error(
+            "🚨 Security Alert: Individual argument exceeds length limit: {}",
+            sanitizeForLogging(arg));
+        throw new SecurityException(
+            "Individual argument exceeds maximum length: " + 
MAX_MAVEN_ARG_LENGTH);
+      }
+
+      if (arg.startsWith("-D")) {
+        // Handle system property definitions
+        validateSystemProperty(arg, i < argArray.length - 1 ? argArray[i + 1] 
: null);
+        validatedArgs.append(arg).append(" ");
+
+        // Skip next argument if it's the value for this -D parameter
+        if (i < argArray.length - 1 && !argArray[i + 1].startsWith("-")) {
+          i++; // Skip the value part
+          validatedArgs.append(argArray[i]).append(" ");
+        }
+      } else if (arg.startsWith("--define")) {
+        // Handle long form system property definitions
+        validateSystemProperty(arg, null);
+        validatedArgs.append(arg).append(" ");
+      } else if (ALLOWED_MAVEN_ARGS.contains(arg)) {
+        // Standard Maven argument
+        validatedArgs.append(arg).append(" ");
+      } else {
+        // Check if it's a Maven lifecycle phase or goal
+        if (isValidMavenPhaseOrGoal(arg)) {
+          validatedArgs.append(arg).append(" ");
+        } else {
+          logSecurityEvent(
+              "VIOLATION", "WHITELIST_VALIDATION", "Unauthorized Maven 
argument detected", arg);
+          throw new SecurityException("Unauthorized Maven argument: " + 
sanitizeForLogging(arg));
+        }
+      }
+    }
+
+    return validatedArgs.toString().trim();
+  }
+
+  /**
+   * Validates system property arguments (-D parameters).
+   *
+   * @param arg the system property argument
+   * @param nextArg the next argument (value) if applicable
+   * @throws SecurityException if invalid system property is detected
+   */
+  private static void validateSystemProperty(String arg, String nextArg) {
+    String propertyDefinition = arg;
+
+    if (arg.equals("-D") && nextArg != null) {
+      propertyDefinition = nextArg;
+    } else if (arg.startsWith("-D")) {
+      propertyDefinition = arg.substring(2);
+    } else if (arg.startsWith("--define=")) {
+      propertyDefinition = arg.substring(9);
+    }
+
+    // Extract property name (before = sign)
+    String propertyName = propertyDefinition.split("=")[0];
+
+    if (!ALLOWED_SYSTEM_PROPERTIES.contains(propertyName)) {
+      // Allow some common patterns but be restrictive
+      if (!isValidSystemPropertyPattern(propertyName)) {
+        logSecurityEvent(
+            "VIOLATION",
+            "SYSTEM_PROPERTY_VALIDATION",
+            "Unauthorized system property detected",
+            propertyName);
+        throw new SecurityException(
+            "Unauthorized system property: " + 
sanitizeForLogging(propertyName));
+      }
+    }
+  }
+
+  /**
+   * Checks if the property name follows safe patterns.
+   *
+   * @param propertyName the property name to validate
+   * @return true if the property pattern is considered safe
+   */
+  private static boolean isValidSystemPropertyPattern(String propertyName) {
+    // Allow properties that start with safe prefixes
+    List<String> safePatterns =
+        Arrays.asList(
+            "maven.",
+            "project.",
+            "flink.",
+            "scala.",
+            "hadoop.",
+            "kafka.",
+            "java.",
+            "user.",
+            "file.",
+            "encoding");
+
+    return safePatterns.stream().anyMatch(propertyName::startsWith)
+        && propertyName.matches("^[a-zA-Z0-9._-]+$"); // Only allow safe 
characters
+  }
+
+  /**
+   * Checks if an argument is a valid Maven lifecycle phase or goal.
+   *
+   * @param arg the argument to check
+   * @return true if it's a valid Maven phase or goal
+   */
+  private static boolean isValidMavenPhaseOrGoal(String arg) {
+    // Basic validation: only allow alphanumeric, hyphens, and colons (for 
plugin goals)
+    return arg.matches("^[a-zA-Z0-9:._-]+$") && arg.length() <= 50;
+  }
+
+  /**
+   * Sanitizes sensitive information for safe logging.
+   *
+   * @param input the input to sanitize
+   * @return sanitized string safe for logging
+   */
+  private static String sanitizeForLogging(String input) {
+    if (input == null) {
+      return "null";
+    }
+
+    // Mask potential sensitive patterns and limit length
+    String sanitized =
+        input
+            .replaceAll("(password|pwd|secret|token|key)=\\S*", "$1=***")
+            .replaceAll("(`[^`]*`)", "***BACKTICK_COMMAND***")
+            .replaceAll("(\\$\\([^)]*\\))", "***COMMAND_SUBSTITUTION***")
+            .replaceAll("(\\$\\{[^}]*})", "***VARIABLE_SUBSTITUTION***");
+
+    // Limit length for logging
+    if (sanitized.length() > 100) {
+      sanitized = sanitized.substring(0, 100) + "...";
+    }
+
+    return sanitized;
+  }
+}
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/entity/Project.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/entity/Project.java
index 1694d9bdb..549ba5a9a 100644
--- 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/entity/Project.java
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/entity/Project.java
@@ -17,13 +17,8 @@
 
 package org.apache.streampark.console.core.entity;
 
-import org.apache.streampark.common.conf.CommonConfig;
-import org.apache.streampark.common.conf.InternalConfigHolder;
 import org.apache.streampark.common.conf.Workspace;
-import org.apache.streampark.common.util.AssertUtils;
-import org.apache.streampark.common.util.Utils;
-import org.apache.streampark.console.base.util.CommonUtils;
-import org.apache.streampark.console.base.util.WebUtils;
+import org.apache.streampark.console.base.util.MavenUtils;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -42,11 +37,7 @@ import org.eclipse.jgit.lib.Constants;
 import java.io.File;
 import java.io.IOException;
 import java.io.Serializable;
-import java.util.Arrays;
 import java.util.Date;
-import java.util.Iterator;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 @Slf4j
 @Getter
@@ -107,8 +98,9 @@ public class Project implements Serializable {
   @JsonIgnore
   public File getAppSource() {
     File sourcePath = new File(Workspace.PROJECT_LOCAL_PATH());
-    if (!sourcePath.exists()) {
-      sourcePath.mkdirs();
+    if (!sourcePath.exists() && !sourcePath.mkdirs()) {
+      throw new IllegalStateException(
+          "Failed to create project source path: " + 
sourcePath.getAbsolutePath());
     } else if (sourcePath.isFile()) {
       throw new IllegalArgumentException(
           "[StreamPark] project source base path: "
@@ -117,18 +109,12 @@ public class Project implements Serializable {
     }
 
     String sourceDir = getSourceDirName();
-    File srcFile =
-        new File(String.format("%s/%s/%s", sourcePath.getAbsolutePath(), name, 
sourceDir));
     String newPath = String.format("%s/%s", sourcePath.getAbsolutePath(), id);
-    if (srcFile.exists()) {
-      File newFile = new File(newPath);
-      if (!newFile.exists()) {
-        newFile.mkdirs();
-      }
-      // old project path move to new path
-      srcFile.getParentFile().renameTo(newFile);
+    File file = new File(newPath, sourceDir);
+    if (!file.exists() && !file.mkdirs()) {
+      throw new IllegalStateException("Failed to create directory: " + 
file.getAbsolutePath());
     }
-    return new File(newPath, sourceDir);
+    return file;
   }
 
   private String getSourceDirName() {
@@ -179,108 +165,20 @@ public class Project implements Serializable {
     }
   }
 
-  @JsonIgnore
-  public String getMavenArgs() {
-    StringBuilder mvnArgBuffer = new StringBuilder(" clean package -DskipTests 
");
-    if (StringUtils.isNotBlank(this.buildArgs)) {
-      mvnArgBuffer.append(this.buildArgs.trim());
-    }
-
-    // --settings
-    String setting = 
InternalConfigHolder.get(CommonConfig.MAVEN_SETTINGS_PATH());
-    if (StringUtils.isNotBlank(setting)) {
-      File file = new File(setting);
-      if (file.exists() && file.isFile()) {
-        mvnArgBuffer.append(" --settings ").append(setting.trim());
-      } else {
-        throw new IllegalArgumentException(
-            String.format(
-                "Invalid maven-setting file path \"%s\", the path not exist or 
is not file",
-                setting));
-      }
-    }
-
-    // check maven args
-    String mvnArgs = mvnArgBuffer.toString();
-    if (mvnArgs.contains("\n")) {
-      throw new IllegalArgumentException(
-          String.format(
-              "Illegal argument: newline character in maven build parameters: 
\"%s\"", mvnArgs));
-    }
-
-    String args = getIllegalArgs(mvnArgs);
-    if (args != null) {
-      throw new IllegalArgumentException(
-          String.format("Illegal argument: \"%s\" in maven build parameters: 
%s", args, mvnArgs));
-    }
-
-    // find mvn
-    boolean windows = Utils.isWindows();
-    String mvn = windows ? "mvn.cmd" : "mvn";
-
-    String mavenHome = System.getenv("M2_HOME");
-    if (mavenHome == null) {
-      mavenHome = System.getenv("MAVEN_HOME");
-    }
-
-    boolean useWrapper = true;
-    if (mavenHome != null) {
-      mvn = mavenHome + "/bin/" + mvn;
-      try {
-        Process process = Runtime.getRuntime().exec(mvn + " --version");
-        process.waitFor();
-        AssertUtils.required(process.exitValue() == 0);
-        useWrapper = false;
-      } catch (Exception ignored) {
-        log.warn("try using user-installed maven failed, now use 
maven-wrapper.");
-      }
-    }
-
-    if (useWrapper) {
-      if (windows) {
-        mvn = WebUtils.getAppHome().concat("/bin/mvnw.cmd");
-      } else {
-        mvn = WebUtils.getAppHome().concat("/bin/mvnw");
-      }
-    }
-    return mvn.concat(mvnArgs);
-  }
-
-  private String getIllegalArgs(String param) {
-    Pattern pattern = Pattern.compile("(`(.?|\\s)*`)|(\\$\\((.?|\\s)*\\))");
-    Matcher matcher = pattern.matcher(param);
-    if (matcher.find()) {
-      return matcher.group(1) == null ? matcher.group(3) : matcher.group(1);
-    }
-
-    Iterator<String> iterator = Arrays.asList(";", "|", "&", ">", 
"<").iterator();
-    String[] argsList = param.split("\\s+");
-    while (iterator.hasNext()) {
-      String chr = iterator.next();
-      for (String arg : argsList) {
-        if (arg.contains(chr)) {
-          return arg;
-        }
-      }
-    }
-    return null;
-  }
-
   @JsonIgnore
   public String getMavenWorkHome() {
     String buildHome = this.getAppSource().getAbsolutePath();
-    if (CommonUtils.notEmpty(this.getPom())) {
-      buildHome =
-          new 
File(buildHome.concat("/").concat(this.getPom())).getParentFile().getAbsolutePath();
+    if (StringUtils.isBlank(this.getPom())) {
+      return buildHome;
     }
-    return buildHome;
+    return new 
File(buildHome.concat("/").concat(this.getPom())).getParentFile().getAbsolutePath();
   }
 
   @JsonIgnore
   public String getLog4BuildStart() {
     return String.format(
         "%sproject : %s\nrefs: %s\ncommand : %s\n\n",
-        getLogHeader("maven install"), getName(), getRefs(), getMavenArgs());
+        getLogHeader("maven install"), getName(), getRefs(), 
this.getMavenBuildArgs());
   }
 
   @JsonIgnore
@@ -294,4 +192,9 @@ public class Project implements Serializable {
   private String getLogHeader(String header) {
     return "---------------------------------[ " + header + " 
]---------------------------------\n";
   }
+
+  @JsonIgnore
+  public String getMavenBuildArgs() {
+    return MavenUtils.getMavenArgs(this.buildArgs);
+  }
 }
diff --git 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/task/ProjectBuildTask.java
 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/task/ProjectBuildTask.java
index 329f47247..88415626e 100644
--- 
a/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/task/ProjectBuildTask.java
+++ 
b/streampark-console/streampark-console-service/src/main/java/org/apache/streampark/console/core/task/ProjectBuildTask.java
@@ -23,204 +23,822 @@ import org.apache.streampark.console.base.util.GitUtils;
 import org.apache.streampark.console.core.entity.Project;
 import org.apache.streampark.console.core.enums.BuildState;
 
+import org.apache.commons.lang3.StringUtils;
+
 import ch.qos.logback.classic.Logger;
 import lombok.extern.slf4j.Slf4j;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.lib.StoredConfig;
 
 import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.regex.Pattern;
 
+/**
+ * This class handles the complete lifecycle of project building including: - 
Git repository
+ * cloning/pulling - Maven/Gradle build execution - Artifact deployment and 
validation -
+ * Comprehensive logging and monitoring
+ */
 @Slf4j
 public class ProjectBuildTask extends AbstractLogFileTask {
 
-  final Project project;
+  // ========== Constants ==========
+
+  private static final Duration CLONE_TIMEOUT = Duration.ofMinutes(10);
+  private static final String BUILD_START_MARKER = "=== BUILD STARTED ===";
+  private static final String BUILD_END_MARKER = "=== BUILD COMPLETED ===";
+  private static final Pattern SENSITIVE_INFO_PATTERN =
+      Pattern.compile("password|token|key|secret", Pattern.CASE_INSENSITIVE);
 
-  final Consumer<BuildState> stateUpdateConsumer;
+  // ========== Fields ==========
 
-  final Consumer<Logger> notifyReleaseConsumer;
+  private final Project project;
+  private final Consumer<BuildState> stateUpdateConsumer;
+  private final Consumer<Logger> notifyReleaseConsumer;
+  private final LocalDateTime startTime = LocalDateTime.now();
 
+  // Build statistics
+  private LocalDateTime cloneStartTime;
+  private LocalDateTime buildStartTime;
+  private LocalDateTime deployStartTime;
+
+  /**
+   * Creates a new ProjectBuildTask with enhanced validation and configuration.
+   *
+   * @param logPath log file path for build output
+   * @param project project to build (must not be null)
+   * @param stateUpdateConsumer callback for build state updates (must not be 
null)
+   * @param notifyReleaseConsumer callback for release notifications (must not 
be null)
+   * @throws IllegalArgumentException if any parameter is null or invalid
+   */
   public ProjectBuildTask(
       String logPath,
       Project project,
       Consumer<BuildState> stateUpdateConsumer,
       Consumer<Logger> notifyReleaseConsumer) {
     super(logPath, true);
-    this.project = project;
-    this.stateUpdateConsumer = stateUpdateConsumer;
-    this.notifyReleaseConsumer = notifyReleaseConsumer;
+
+    // Enhanced parameter validation
+    this.project = validateProject(project);
+    this.stateUpdateConsumer =
+        Objects.requireNonNull(stateUpdateConsumer, "State update consumer 
cannot be null");
+    this.notifyReleaseConsumer =
+        Objects.requireNonNull(notifyReleaseConsumer, "Notify release consumer 
cannot be null");
+
+    // Validate log path
+    validateLogPath(logPath);
+
+    log.info(
+        "ProjectBuildTask initialized for project: {} (ID: {})",
+        project.getName(),
+        project.getId());
   }
 
+  // ========== Main Execution Flow ==========
+
   @Override
   protected void doRun() throws Throwable {
-    log.info("Project {} start build", project.getName());
-    fileLogger.info(project.getLog4BuildStart());
-    boolean cloneSuccess = cloneSourceCode(project);
-    if (!cloneSuccess) {
-      fileLogger.error("[StreamPark] clone or pull error.");
-      stateUpdateConsumer.accept(BuildState.FAILED);
-      return;
-    }
-    boolean build = projectBuild(project);
-    if (!build) {
-      stateUpdateConsumer.accept(BuildState.FAILED);
-      fileLogger.error("build error, project name: {} ", project.getName());
-      return;
+    try {
+      logBuildStart();
+      updateBuildState(BuildState.BUILDING);
+
+      // Phase 1: Clone source code with retry mechanism
+      if (!execute("clone", this::performClone)) {
+        handleBuildFailure("Failed to clone source code");
+        return;
+      }
+
+      // Phase 2: Execute build with timeout and monitoring
+      if (!execute("build", this::performBuild)) {
+        handleBuildFailure("Failed to build project");
+        return;
+      }
+
+      // Phase 3: Deploy artifacts
+      if (!execute("deploy", this::performDeploy)) {
+        handleBuildFailure("Failed to deploy artifacts");
+        return;
+      }
+
+      // Success
+      handleBuildSuccess();
+
+    } catch (Exception e) {
+      if (e instanceof InterruptedException) {
+        Thread.currentThread().interrupt();
+        handleBuildInterruption();
+      } else {
+        handleBuildFailure("Unexpected error during build: " + e.getMessage(), 
e);
+      }
     }
-    stateUpdateConsumer.accept(BuildState.SUCCESSFUL);
-    this.deploy(project);
-    notifyReleaseConsumer.accept(fileLogger);
   }
 
   @Override
   protected void processException(Throwable t) {
-    stateUpdateConsumer.accept(BuildState.FAILED);
-    fileLogger.error("Build error, project name: {}", project.getName(), t);
+    log.error("Build task exception for project: {}", project.getName(), t);
+    updateBuildState(BuildState.FAILED);
+    if (t instanceof Exception) {
+      logBuildError("Build failed with exception", (Exception) t);
+    } else {
+      fileLogger.error("Build failed with throwable: {}", t.getMessage(), t);
+    }
   }
 
   @Override
-  protected void doFinally() {}
+  protected void doFinally() {
+    try {
+      cleanupResources();
+      logBuildSummary();
+    } catch (Exception e) {
+      log.warn("Error during cleanup for project: {}", project.getName(), e);
+    }
+  }
+
+  // ========== Clone Phase Implementation ==========
+
+  private boolean performClone() throws Exception {
+    cloneStartTime = LocalDateTime.now();
+
+    try {
+      validatePreCloneConditions();
+      prepareCloneDirectory();
+
+      boolean success = executeGitClone();
+      if (success) {
+        logCloneSuccess();
+      }
+
+      return success;
 
-  private boolean cloneSourceCode(Project project) {
+    } catch (Exception e) {
+      logCloneError("Git clone operation failed", e);
+      throw e;
+    }
+  }
+
+  private void validatePreCloneConditions() throws IllegalStateException {
+    if (StringUtils.isBlank(project.getUrl())) {
+      throw new IllegalStateException("Project URL cannot be blank");
+    }
+
+    if (project.getAppSource() == null || !project.getAppSource().exists()) {
+      throw new IllegalStateException("Project source directory does not 
exist");
+    }
+
+    // Validate URL format (basic validation)
+    if (!project.getUrl().startsWith("http") && 
!project.getUrl().startsWith("git@")) {
+      throw new IllegalStateException("Invalid Git URL format: " + 
sanitizeUrl(project.getUrl()));
+    }
+  }
+
+  private void prepareCloneDirectory() throws IOException {
     try {
       project.cleanCloned();
-      fileLogger.info("clone {}, {} starting...", project.getName(), 
project.getUrl());
+
+      // Ensure parent directory exists
+      Path sourcePath = project.getAppSource().toPath();
+      Path parentDir = sourcePath.getParent();
+      if (parentDir != null && !Files.exists(parentDir)) {
+        Files.createDirectories(parentDir);
+        fileLogger.info("Created parent directory: {}", parentDir);
+      }
+
+    } catch (Exception e) {
+      throw new IOException("Failed to prepare clone directory: " + 
e.getMessage(), e);
+    }
+  }
+
+  private boolean executeGitClone() {
+    Git git = null;
+    try {
+      fileLogger.info("Starting Git clone for project: {}", project.getName());
+      fileLogger.info("Repository URL: {}", sanitizeUrl(project.getUrl()));
+      fileLogger.info("Target directory: {}", project.getAppSource());
+      fileLogger.info("Branch/Tag: {}", 
StringUtils.defaultIfBlank(project.getRefs(), "default"));
       fileLogger.info(project.getLog4CloneStart());
 
-      GitUtils.GitCloneRequest request = new GitUtils.GitCloneRequest();
-      request.setUrl(project.getUrl());
-      request.setRefs(project.getRefs());
-      request.setStoreDir(project.getAppSource());
-      request.setUsername(project.getUserName());
-      request.setPassword(project.getPassword());
-      request.setPrivateKey(project.getPrvkeyPath());
-
-      Git git = GitUtils.clone(request);
-      StoredConfig config = git.getRepository().getConfig();
-      config.setBoolean("http", project.getUrl(), "sslVerify", false);
-      config.setBoolean("https", project.getUrl(), "sslVerify", false);
-      config.save();
-      File workTree = git.getRepository().getWorkTree();
-      printWorkTree(workTree, "");
-      String successMsg =
-          String.format("[StreamPark] project [%s] git clone successful!\n", 
project.getName());
-      fileLogger.info(successMsg);
-      git.close();
+      GitUtils.GitCloneRequest request = buildGitCloneRequest();
+      git = GitUtils.clone(request);
+
+      configureGitRepository(git);
+      validateCloneContent(git);
+
       return true;
+
     } catch (Exception e) {
-      fileLogger.error(
-          String.format(
-              "[StreamPark] project [%s] refs [%s] git clone failed, err: %s",
-              project.getName(), project.getRefs(), e));
-      fileLogger.error(String.format("project %s clone error ", 
project.getName()), e);
+      logCloneError("Git clone failed", e);
       return false;
+    } finally {
+      if (git != null) {
+        try {
+          git.close();
+        } catch (Exception e) {
+          log.warn("Failed to close Git repository: {}", e.getMessage());
+        }
+      }
     }
   }
 
-  private void printWorkTree(File workTree, String space) {
+  private GitUtils.GitCloneRequest buildGitCloneRequest() {
+    GitUtils.GitCloneRequest request = new GitUtils.GitCloneRequest();
+    request.setUrl(project.getUrl());
+    request.setRefs(project.getRefs());
+    request.setStoreDir(project.getAppSource());
+    request.setUsername(project.getUserName());
+    request.setPassword(project.getPassword());
+    request.setPrivateKey(project.getPrvkeyPath());
+    return request;
+  }
+
+  private void configureGitRepository(Git git) throws Exception {
+    StoredConfig config = git.getRepository().getConfig();
+    String url = project.getUrl();
+
+    // Disable SSL verification for HTTP/HTTPS URLs
+    config.setBoolean("http", url, "sslVerify", false);
+    config.setBoolean("https", url, "sslVerify", false);
+
+    // Set timeout configurations
+    config.setInt("http", url, "timeout", (int) CLONE_TIMEOUT.getSeconds());
+    config.setInt("https", url, "timeout", (int) CLONE_TIMEOUT.getSeconds());
+
+    config.save();
+    fileLogger.info("Git repository configuration updated successfully");
+  }
+
+  private void validateCloneContent(Git git) {
+    File workTree = git.getRepository().getWorkTree();
+
+    if (!workTree.exists() || !workTree.isDirectory()) {
+      throw new IllegalStateException("Clone directory does not exist or is 
not a directory");
+    }
+
     File[] files = workTree.listFiles();
-    for (File file : Objects.requireNonNull(files)) {
-      if (!file.getName().startsWith(".git")) {
-        continue;
-      }
-      if (file.isFile()) {
-        fileLogger.info("{} / {}", space, file.getName());
-      } else if (file.isDirectory()) {
-        fileLogger.info("{} / {}", space, file.getName());
-        printWorkTree(file, space.concat("/").concat(file.getName()));
+    if (files == null || files.length == 0) {
+      throw new IllegalStateException("Cloned repository is empty");
+    }
+
+    // Log directory structure (limited depth for security)
+    logDirectoryStructure(workTree, "", 0, 3);
+
+    fileLogger.info("Clone validation completed. Found {} files/directories", 
files.length);
+  }
+
+  // ========== Build Phase Implementation ==========
+
+  private boolean performBuild() throws Exception {
+    buildStartTime = LocalDateTime.now();
+
+    try {
+      validatePreBuildConditions();
+      boolean success = executeMavenBuild();
+
+      if (success) {
+        Duration buildDuration = Duration.between(buildStartTime, 
LocalDateTime.now());
+        fileLogger.info("Maven build completed successfully in {}", 
formatDuration(buildDuration));
       }
+
+      return success;
+
+    } catch (Exception e) {
+      logBuildError("Maven build failed", e);
+      throw e;
+    }
+  }
+
+  private void validatePreBuildConditions() throws IllegalStateException {
+    String mavenWorkHome = project.getMavenWorkHome();
+    if (StringUtils.isBlank(mavenWorkHome)) {
+      throw new IllegalStateException("Maven work home cannot be blank");
+    }
+
+    File workDir = new File(mavenWorkHome);
+    if (!workDir.exists()) {
+      throw new IllegalStateException("Maven work directory does not exist: " 
+ mavenWorkHome);
+    }
+
+    // Check for pom.xml or build.gradle
+    File pomFile = new File(workDir, "pom.xml");
+    File gradleFile = new File(workDir, "build.gradle");
+    if (!pomFile.exists() && !gradleFile.exists()) {
+      throw new IllegalStateException(
+          "No build file (pom.xml or build.gradle) found in: " + 
mavenWorkHome);
+    }
+
+    String mavenArgs = project.getMavenBuildArgs();
+    if (StringUtils.isBlank(mavenArgs)) {
+      throw new IllegalStateException("Maven arguments cannot be blank");
+    }
+  }
+
+  private boolean executeMavenBuild() {
+    try {
+      fileLogger.info(BUILD_START_MARKER);
+      fileLogger.info("🔨 Starting Maven build for project: {}", 
project.getName());
+      fileLogger.info("   📂 Working directory: {}", 
project.getMavenWorkHome());
+      fileLogger.info(
+          "   âš™ī¸  Build command: {}", 
sanitizeBuildCommand(project.getMavenBuildArgs()));
+
+      int exitCode =
+          CommandUtils.execute(
+              project.getMavenWorkHome(),
+              Collections.singletonList(project.getMavenBuildArgs()),
+              this::logBuildOutput);
+
+      fileLogger.info("Maven build completed with exit code: {}", exitCode);
+      fileLogger.info(BUILD_END_MARKER);
+
+      return exitCode == 0;
+
+    } catch (Exception e) {
+      fileLogger.error("Maven build execution failed: {}", e.getMessage());
+      return false;
     }
   }
 
-  private boolean projectBuild(Project project) {
-    int code =
-        CommandUtils.execute(
-            project.getMavenWorkHome(),
-            Collections.singletonList(project.getMavenArgs()),
-            (line) -> fileLogger.info(line));
-    return code == 0;
+  private void logBuildOutput(String line) {
+    if (StringUtils.isNotBlank(line)) {
+      // Filter sensitive information
+      String sanitizedLine = sanitizeBuildOutput(line);
+      fileLogger.info(sanitizedLine);
+    }
   }
 
-  private void deploy(Project project) throws Exception {
-    File path = project.getAppSource();
-    List<File> apps = new ArrayList<>();
-    // find the compiled tar.gz (Stream Park project) file or jar (normal or 
official standard flink
-    // project) under the project path
-    findTarOrJar(apps, path);
-    if (apps.isEmpty()) {
+  // ========== Deploy Phase Implementation ==========
+
+  private boolean performDeploy() throws Exception {
+    deployStartTime = LocalDateTime.now();
+
+    try {
+      validatePreDeployConditions();
+      deployBuildArtifacts();
+      validateDeploymentResult();
+      logDeploySuccess();
+
+      return true;
+
+    } catch (Exception e) {
+      logDeployError("Deployment failed", e);
+      throw e;
+    }
+  }
+
+  private void validatePreDeployConditions() throws IllegalStateException {
+    File sourceDir = project.getAppSource();
+    if (sourceDir == null || !sourceDir.exists()) {
+      throw new IllegalStateException("Source directory does not exist");
+    }
+
+    File distHome = project.getDistHome();
+    if (distHome == null) {
+      throw new IllegalStateException("Distribution home directory is not 
configured");
+    }
+  }
+
+  private void deployBuildArtifacts() throws Exception {
+    File sourcePath = project.getAppSource();
+    List<File> artifacts = new ArrayList<>();
+
+    // Find build artifacts with improved algorithm
+    findBuildArtifacts(artifacts, sourcePath, 0, 5); // Limit depth to 5
+
+    if (artifacts.isEmpty()) {
       throw new RuntimeException(
-          "[StreamPark] can't find tar.gz or jar in " + 
path.getAbsolutePath());
-    }
-    for (File app : apps) {
-      String appPath = app.getAbsolutePath();
-      // 1). tar.gz file
-      if (appPath.endsWith("tar.gz")) {
-        File deployPath = project.getDistHome();
-        if (!deployPath.exists()) {
-          deployPath.mkdirs();
-        }
-        // xzvf jar
-        if (app.exists()) {
-          String cmd =
-              String.format(
-                  "tar -xzvf %s -C %s", app.getAbsolutePath(), 
deployPath.getAbsolutePath());
-          CommandUtils.execute(cmd);
-        }
-      } else {
-        // 2) .jar file(normal or official standard flink project)
-        Utils.checkJarFile(app.toURI().toURL());
-        String moduleName = app.getName().replace(".jar", "");
-        File distHome = project.getDistHome();
-        File targetDir = new File(distHome, moduleName);
-        if (!targetDir.exists()) {
-          targetDir.mkdirs();
-        }
-        File targetJar = new File(targetDir, app.getName());
-        app.renameTo(targetJar);
+          "No deployable artifacts (*.tar.gz or *.jar) found in project 
directory: "
+              + sourcePath.getAbsolutePath());
+    }
+
+    fileLogger.info("🔍 Found {} deployable artifact(s)", artifacts.size());
+
+    for (File artifact : artifacts) {
+      deployArtifact(artifact);
+    }
+  }
+
+  private void deployArtifact(File artifact) throws Exception {
+    String artifactPath = artifact.getAbsolutePath();
+    fileLogger.info("đŸ“Ļ Deploying artifact: {}", artifactPath);
+
+    if (artifactPath.endsWith(".tar.gz")) {
+      deployTarGzArtifact(artifact);
+    } else if (artifactPath.endsWith(".jar")) {
+      deployJarArtifact(artifact);
+    } else {
+      throw new IllegalArgumentException("Unsupported artifact type: " + 
artifactPath);
+    }
+
+    fileLogger.info("✅ Successfully deployed artifact: {}", 
artifact.getName());
+  }
+
+  private void deployTarGzArtifact(File tarGzFile) throws Exception {
+    File deployPath = project.getDistHome();
+    ensureDirectoryExists(deployPath);
+
+    if (!tarGzFile.exists()) {
+      throw new IllegalStateException("Tar.gz file does not exist: " + 
tarGzFile.getAbsolutePath());
+    }
+
+    String extractCommand =
+        String.format(
+            "tar -xzf %s -C %s", tarGzFile.getAbsolutePath(), 
deployPath.getAbsolutePath());
+
+    fileLogger.info("đŸ“Ļ Extracting tar.gz: {}", extractCommand);
+
+    CommandUtils.execute(extractCommand);
+  }
+
+  private void deployJarArtifact(File jarFile) throws Exception {
+    // Validate JAR file integrity
+    Utils.checkJarFile(jarFile.toURI().toURL());
+
+    String moduleName = jarFile.getName().replace(".jar", "");
+    File distHome = project.getDistHome();
+    File targetDir = new File(distHome, moduleName);
+
+    ensureDirectoryExists(targetDir);
+
+    File targetJar = new File(targetDir, jarFile.getName());
+
+    // Use Files.move for atomic operation and better error handling
+    try {
+      Files.move(jarFile.toPath(), targetJar.toPath());
+      fileLogger.info("đŸ“Ļ JAR artifact moved to: {}", 
targetJar.getAbsolutePath());
+    } catch (IOException e) {
+      throw new IOException("Failed to move JAR artifact: " + e.getMessage(), 
e);
+    }
+  }
+
+  private void validateDeploymentResult() throws Exception {
+    File distHome = project.getDistHome();
+    if (!distHome.exists()) {
+      throw new IllegalStateException("Deployment directory was not created");
+    }
+
+    File[] deployedFiles = distHome.listFiles();
+    if (deployedFiles == null || deployedFiles.length == 0) {
+      throw new IllegalStateException("No files were deployed");
+    }
+
+    fileLogger.info("✅ Deployment validation completed. {} items deployed", 
deployedFiles.length);
+  }
+
+  // ========== Utility Methods ==========
+
+  private boolean execute(String operationName, ExecuteOperation operation) {
+    try {
+      boolean success = operation.execute();
+      if (success) {
+        fileLogger.info("{} operation succeeded", operationName);
+        return true;
       }
+      fileLogger.warn("{} operation failed", operationName);
+    } catch (InterruptedException e) {
+      Thread.currentThread().interrupt();
+      fileLogger.error("{} operation interrupted", operationName);
+      return false;
+    } catch (Exception e) {
+      fileLogger.error("{} operation failed: {}", operationName, 
e.getMessage());
     }
+    return false;
   }
 
-  private void findTarOrJar(List<File> list, File path) {
-    for (File file : path.listFiles()) {
-      // navigate to the target directory:
+  private void findBuildArtifacts(
+      List<File> artifacts, File directory, int currentDepth, int maxDepth) {
+    if (currentDepth >= maxDepth || !directory.isDirectory()) {
+      return;
+    }
+
+    File[] files = directory.listFiles();
+    if (files == null) {
+      return;
+    }
+
+    for (File file : files) {
       if (file.isDirectory()) {
         if ("target".equals(file.getName())) {
-          // find the tar.gz file or the jar file in the target path.
-          // note: only one of the two can be selected, which cannot be 
satisfied at the same time.
-          File tar = null;
-          File jar = null;
-          for (File targetFile : file.listFiles()) {
-            // 1) exit once the tar.gz file is found.
-            if (targetFile.getName().endsWith("tar.gz")) {
-              tar = targetFile;
-              break;
-            }
-            // 2) try look for jar files, there may be multiple jars found.
-            if (!targetFile.getName().startsWith("original-")
-                && !targetFile.getName().endsWith("-sources.jar")
-                && targetFile.getName().endsWith(".jar")) {
-              if (jar == null) {
-                jar = targetFile;
-              } else {
-                if (targetFile.length() > jar.length()) {
-                  jar = targetFile;
-                }
-              }
-            }
-          }
-          File target = tar == null ? jar : tar;
-          if (target != null) {
-            list.add(target);
-          }
-        } else {
-          findTarOrJar(list, file);
+          findArtifactsInTargetDirectory(artifacts, file);
+        } else if (!file.getName().startsWith(".")) {
+          // Recursive search in non-hidden directories
+          findBuildArtifacts(artifacts, file, currentDepth + 1, maxDepth);
+        }
+      }
+    }
+  }
+
+  private void findArtifactsInTargetDirectory(List<File> artifacts, File 
targetDir) {
+    File[] files = targetDir.listFiles();
+    if (files == null) {
+      return;
+    }
+
+    File tarGzFile = null;
+    File jarFile = null;
+
+    for (File file : files) {
+      String fileName = file.getName();
+
+      // Priority 1: tar.gz files
+      if (fileName.endsWith(".tar.gz")) {
+        tarGzFile = file;
+        break; // tar.gz has highest priority
+      }
+
+      // Priority 2: JAR files (excluding sources and original)
+      if (fileName.endsWith(".jar")
+          && !fileName.startsWith("original-")
+          && !fileName.endsWith("-sources.jar")
+          && !fileName.endsWith("-javadoc.jar")) {
+
+        if (jarFile == null || file.length() > jarFile.length()) {
+          jarFile = file; // Keep the largest JAR
         }
       }
     }
+
+    File selectedArtifact = tarGzFile != null ? tarGzFile : jarFile;
+    if (selectedArtifact != null) {
+      artifacts.add(selectedArtifact);
+      fileLogger.info(
+          "📄 Found build artifact: {} ({})",
+          selectedArtifact.getName(),
+          formatFileSize(selectedArtifact.length()));
+    }
+  }
+
+  private void logDirectoryStructure(
+      File directory, String indent, int currentDepth, int maxDepth) {
+    if (currentDepth >= maxDepth || !directory.isDirectory()) {
+      return;
+    }
+
+    File[] files = directory.listFiles();
+    if (files == null) {
+      return;
+    }
+
+    for (File file : files) {
+      // Only show git-related files and important build files
+      if (file.getName().startsWith(".git")
+          || file.getName().equals("pom.xml")
+          || file.getName().equals("build.gradle")
+          || file.getName().equals("target")
+          || file.getName().equals("src")) {
+
+        String type = file.isDirectory() ? "/" : "";
+        fileLogger.info("{}├── {}{}", indent, file.getName(), type);
+
+        if (file.isDirectory() && currentDepth < maxDepth - 1) {
+          logDirectoryStructure(file, indent + "│   ", currentDepth + 1, 
maxDepth);
+        }
+      }
+    }
+  }
+
+  // ========== State Management ==========
+
+  private void updateBuildState(BuildState state) {
+    try {
+      stateUpdateConsumer.accept(state);
+    } catch (Exception e) {
+      log.warn("Failed to update build state to {}: {}", state, 
e.getMessage());
+    }
+  }
+
+  private void handleBuildSuccess() {
+    updateBuildState(BuildState.SUCCESSFUL);
+    fileLogger.info("=== BUILD SUCCESSFUL ===");
+    fileLogger.info(
+        "Project {} built successfully in {}",
+        project.getName(),
+        formatDuration(Duration.between(startTime, LocalDateTime.now())));
+
+    try {
+      notifyReleaseConsumer.accept(fileLogger);
+    } catch (Exception e) {
+      log.warn("Failed to notify release consumer: {}", e.getMessage());
+    }
+  }
+
+  private void handleBuildFailure(String message) {
+    handleBuildFailure(message, null);
+  }
+
+  private void handleBuildFailure(String message, Throwable cause) {
+    updateBuildState(BuildState.FAILED);
+    fileLogger.error("=== BUILD FAILED ===");
+    fileLogger.error("Project {} build failed: {}", project.getName(), 
message);
+
+    if (cause != null) {
+      fileLogger.error("Cause: {}", cause.getMessage());
+    }
+  }
+
+  private void handleBuildInterruption() {
+    updateBuildState(BuildState.FAILED);
+    fileLogger.warn("=== BUILD INTERRUPTED ===");
+    fileLogger.warn("Project {} build was interrupted", project.getName());
+  }
+
+  // ========== Logging Methods ==========
+
+  private void logBuildStart() {
+    fileLogger.info("===============================================");
+    fileLogger.info("StreamPark Project Build Started");
+    fileLogger.info("===============================================");
+    fileLogger.info("Project: {}", project.getName());
+    fileLogger.info("ID: {}", project.getId());
+    fileLogger.info("Start Time: {}", 
startTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+    fileLogger.info("Repository: {}", sanitizeUrl(project.getUrl()));
+    fileLogger.info("Branch/Tag: {}", 
StringUtils.defaultIfBlank(project.getRefs(), "default"));
+    fileLogger.info("Build Args: {}", 
sanitizeBuildCommand(project.getMavenBuildArgs()));
+    fileLogger.info("===============================================");
+    fileLogger.info(project.getLog4BuildStart());
+  }
+
+  private void logBuildSummary() {
+    LocalDateTime endTime = LocalDateTime.now();
+    Duration totalDuration = Duration.between(startTime, endTime);
+
+    fileLogger.info("===============================================");
+    fileLogger.info("Build Summary for Project: {}", project.getName());
+    fileLogger.info("===============================================");
+    fileLogger.info("Total Duration: {}", formatDuration(totalDuration));
+    fileLogger.info("End Time: {}", 
endTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
+    fileLogger.info("===============================================");
+  }
+
+  private void logCloneSuccess() {
+    if (cloneStartTime != null) {
+      Duration cloneDuration = Duration.between(cloneStartTime, 
LocalDateTime.now());
+      fileLogger.info("Git clone completed successfully in {}", 
formatDuration(cloneDuration));
+      fileLogger.info(
+          String.format("[StreamPark] project [%s] git clone successful!", 
project.getName()));
+    }
+  }
+
+  private void logCloneError(String message, Exception e) {
+    fileLogger.error("[StreamPark] {}: {}", message, e.getMessage());
+    fileLogger.error(
+        "Project: {}, Refs: {}, URL: {}",
+        project.getName(),
+        project.getRefs(),
+        sanitizeUrl(project.getUrl()));
+  }
+
+  private void logBuildSuccess() {
+    if (buildStartTime != null) {}
+  }
+
+  private void logBuildError(String message, Exception e) {
+    fileLogger.error("[StreamPark] {}: {}", message, e.getMessage());
+    fileLogger.error(
+        "Project: {}, Working Directory: {}", project.getName(), 
project.getMavenWorkHome());
+  }
+
+  private void logDeploySuccess() {
+    if (deployStartTime != null) {
+      Duration deployDuration = Duration.between(deployStartTime, 
LocalDateTime.now());
+      fileLogger.info("Deployment completed successfully in {}", 
formatDuration(deployDuration));
+    }
+  }
+
+  private void logDeployError(String message, Exception e) {
+    fileLogger.error("[StreamPark] {}: {}", message, e.getMessage());
+    fileLogger.error(
+        "Project: {}, Dist Home: {}", project.getName(), 
project.getDistHome().getAbsolutePath());
+  }
+
+  // ========== Validation Methods ==========
+
+  private Project validateProject(Project project) {
+    Objects.requireNonNull(project, "Project cannot be null");
+
+    if (project.getId() == null) {
+      throw new IllegalArgumentException("Project ID cannot be null");
+    }
+
+    if (StringUtils.isBlank(project.getName())) {
+      throw new IllegalArgumentException("Project name cannot be blank");
+    }
+
+    return project;
+  }
+
+  private void validateLogPath(String logPath) {
+    if (StringUtils.isBlank(logPath)) {
+      throw new IllegalArgumentException("Log path cannot be blank");
+    }
+
+    try {
+      Path path = Paths.get(logPath);
+      Path parent = path.getParent();
+      if (parent != null && !Files.exists(parent)) {
+        Files.createDirectories(parent);
+      }
+    } catch (Exception e) {
+      throw new IllegalArgumentException("Invalid log path: " + logPath, e);
+    }
+  }
+
+  // ========== Utility Helper Methods ==========
+
+  private void ensureDirectoryExists(File directory) throws IOException {
+    if (!directory.exists() && !directory.mkdirs()) {
+      throw new IOException("Failed to create directory: " + 
directory.getAbsolutePath());
+    }
+  }
+
+  private void cleanupResources() {
+    // Cleanup any temporary files or resources
+    try {
+      // Force garbage collection to clean up any file handles
+      System.gc();
+    } catch (Exception e) {
+      log.debug("Error during resource cleanup: {}", e.getMessage());
+    }
+  }
+
+  private String sanitizeUrl(String url) {
+    if (StringUtils.isBlank(url)) {
+      return "[BLANK_URL]";
+    }
+
+    // Remove credentials from URL for logging
+    return url.replaceAll("://[^@/]+@", "://***@");
+  }
+
+  private String sanitizeBuildCommand(String command) {
+    if (StringUtils.isBlank(command)) {
+      return "[NO_COMMAND]";
+    }
+
+    // Remove sensitive information from build commands
+    return SENSITIVE_INFO_PATTERN.matcher(command).replaceAll("***");
+  }
+
+  private String sanitizeBuildOutput(String output) {
+    if (StringUtils.isBlank(output)) {
+      return "";
+    }
+
+    // Remove sensitive information from build output
+    return SENSITIVE_INFO_PATTERN.matcher(output).replaceAll("***");
+  }
+
+  private String formatDuration(Duration duration) {
+    long seconds = duration.getSeconds();
+    long minutes = seconds / 60;
+    seconds = seconds % 60;
+
+    if (minutes > 0) {
+      return String.format("%dm %ds", minutes, seconds);
+    } else {
+      return String.format("%ds", seconds);
+    }
+  }
+
+  private String formatFileSize(long bytes) {
+    if (bytes < 1024) {
+      return bytes + " B";
+    } else if (bytes < 1024 * 1024) {
+      return String.format("%.1f KB", bytes / 1024.0);
+    } else {
+      return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
+    }
+  }
+
+  /**
+   * Truncates a string to the specified length, adding ellipsis if necessary.
+   *
+   * @param str the string to truncate
+   * @param maxLength maximum length allowed
+   * @return truncated string
+   */
+  private String truncateString(String str, int maxLength) {
+    if (str == null) {
+      return "";
+    }
+    if (str.length() <= maxLength) {
+      return str;
+    }
+    return str.substring(0, Math.max(0, maxLength - 3)) + "...";
+  }
+
+  /** Functional interface for retryable operations. */
+  @FunctionalInterface
+  private interface ExecuteOperation {
+
+    boolean execute() throws Exception;
   }
 }

Reply via email to