ruanwenjun commented on code in PR #17711:
URL: 
https://github.com/apache/dolphinscheduler/pull/17711#discussion_r2559361534


##########
dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/expand/CuringParamsServiceImpl.java:
##########
@@ -160,107 +161,177 @@ public Map<String, Property> 
parseWorkflowFatherParam(@Nullable Map<String, Stri
     }
 
     /**
-     * Generate prepare params include project params, global parameters, 
local parameters, built-in parameters, varpool, start-up params.
-     * <p> The priority of the parameters is as follows:
-     * <p> varpool > command parameters > local parameters > global parameters 
> project parameters > built-in parameters
-     * todo: Use TaskRuntimeParams to represent this.
+     * Prepares the final map of task execution parameters by merging 
parameters from multiple sources
+     * in a well-defined priority order. The resulting map is guaranteed to 
contain only valid entries:
+     * <ul>
+     *   <li>Keys are non-null and non-blank strings</li>
+     *   <li>Values are non-null {@link Property} objects</li>
+     * </ul>
      *
-     * @param taskInstance
-     * @param parameters
-     * @param workflowInstance
-     * @param projectName
-     * @param workflowDefinitionName
-     * @return
+     * <p><strong>Parameter Precedence (highest to lowest):</strong>
+     * <ol>
+     *   <li>Business/scheduling time parameters (e.g., {@code 
${system.datetime}})</li>
+     *   <li>Command-line or runtime complement parameters</li>
+     *   <li>Task-local parameters</li>
+     *   <li>Workflow global parameters (solidified at instance creation)</li>
+     *   <li>Project-level parameters</li>
+     *   <li>Built-in system parameters (e.g., {@code ${task.id}})</li>
+     * </ol>
+     *
+     * <p><strong>Important Notes:</strong>
+     * <ul>
+     *   <li>All parameter sources are sanitized via {@link #safePutAll(Map, 
Map)} to prevent {@code null}
+     *       or blank keys, which would cause JSON serialization failures 
(e.g., Jackson's
+     *       "Null key for a Map not allowed in JSON").</li>
+     *   <li>Placeholders (e.g., {@code "${var}"}) in parameter values are 
resolved after all sources
+     *       are merged, using the consolidated parameter map. Global 
parameters are already
+     *       <em>solidified</em> (fully resolved at workflow instance 
creation), so no recursive
+     *       placeholder expansion is required.</li>
+     *   <li>{@code VarPool} values (from upstream tasks) only override 
parameters marked as
+     *       {@link Direct#IN}; output or constant parameters remain 
unchanged.</li>
+     * </ul>
+     *
+     * @param taskInstance           the current task instance (must not be 
null)
+     * @param parameters             the parsed task-specific parameters (must 
not be null)
+     * @param workflowInstance       the parent workflow instance (must not be 
null)
+     * @param projectName            name of the project containing the 
workflow
+     * @param workflowDefinitionName name of the workflow definition
+     * @return a safe, fully resolved map of parameter name to {@link 
Property}, ready for task execution
      */
     @Override
-    public Map<String, Property> paramParsingPreparation(@NonNull TaskInstance 
taskInstance,
+    public Map<String, Property> paramParsingPreparation(
+                                                         @NonNull TaskInstance 
taskInstance,
                                                          @NonNull 
AbstractParameters parameters,
                                                          @NonNull 
WorkflowInstance workflowInstance,
                                                          String projectName,
                                                          String 
workflowDefinitionName) {
-        Map<String, Property> prepareParamsMap = new HashMap<>();
 
-        // assign value to definedParams here
-        Map<String, Property> globalParams = 
parseGlobalParamsMap(workflowInstance);
-
-        // combining local and global parameters
-        Map<String, Property> localParams = 
parameters.getInputLocalParametersMap();
-
-        // stream pass params
-        List<Property> varPools = parseVarPool(taskInstance);
+        Map<String, Property> prepareParamsMap = new HashMap<>();

Review Comment:
   Revert the unrelated changes.



##########
dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/expand/CuringParamsServiceImpl.java:
##########
@@ -160,107 +161,177 @@ public Map<String, Property> 
parseWorkflowFatherParam(@Nullable Map<String, Stri
     }
 
     /**
-     * Generate prepare params include project params, global parameters, 
local parameters, built-in parameters, varpool, start-up params.
-     * <p> The priority of the parameters is as follows:
-     * <p> varpool > command parameters > local parameters > global parameters 
> project parameters > built-in parameters
-     * todo: Use TaskRuntimeParams to represent this.
+     * Prepares the final map of task execution parameters by merging 
parameters from multiple sources
+     * in a well-defined priority order. The resulting map is guaranteed to 
contain only valid entries:
+     * <ul>
+     *   <li>Keys are non-null and non-blank strings</li>
+     *   <li>Values are non-null {@link Property} objects</li>
+     * </ul>
      *
-     * @param taskInstance
-     * @param parameters
-     * @param workflowInstance
-     * @param projectName
-     * @param workflowDefinitionName
-     * @return
+     * <p><strong>Parameter Precedence (highest to lowest):</strong>
+     * <ol>
+     *   <li>Business/scheduling time parameters (e.g., {@code 
${system.datetime}})</li>
+     *   <li>Command-line or runtime complement parameters</li>
+     *   <li>Task-local parameters</li>
+     *   <li>Workflow global parameters (solidified at instance creation)</li>
+     *   <li>Project-level parameters</li>
+     *   <li>Built-in system parameters (e.g., {@code ${task.id}})</li>
+     * </ol>
+     *
+     * <p><strong>Important Notes:</strong>
+     * <ul>
+     *   <li>All parameter sources are sanitized via {@link #safePutAll(Map, 
Map)} to prevent {@code null}
+     *       or blank keys, which would cause JSON serialization failures 
(e.g., Jackson's
+     *       "Null key for a Map not allowed in JSON").</li>
+     *   <li>Placeholders (e.g., {@code "${var}"}) in parameter values are 
resolved after all sources
+     *       are merged, using the consolidated parameter map. Global 
parameters are already
+     *       <em>solidified</em> (fully resolved at workflow instance 
creation), so no recursive
+     *       placeholder expansion is required.</li>
+     *   <li>{@code VarPool} values (from upstream tasks) only override 
parameters marked as
+     *       {@link Direct#IN}; output or constant parameters remain 
unchanged.</li>
+     * </ul>
+     *
+     * @param taskInstance           the current task instance (must not be 
null)
+     * @param parameters             the parsed task-specific parameters (must 
not be null)
+     * @param workflowInstance       the parent workflow instance (must not be 
null)
+     * @param projectName            name of the project containing the 
workflow
+     * @param workflowDefinitionName name of the workflow definition
+     * @return a safe, fully resolved map of parameter name to {@link 
Property}, ready for task execution
      */
     @Override
-    public Map<String, Property> paramParsingPreparation(@NonNull TaskInstance 
taskInstance,
+    public Map<String, Property> paramParsingPreparation(
+                                                         @NonNull TaskInstance 
taskInstance,
                                                          @NonNull 
AbstractParameters parameters,
                                                          @NonNull 
WorkflowInstance workflowInstance,
                                                          String projectName,
                                                          String 
workflowDefinitionName) {
-        Map<String, Property> prepareParamsMap = new HashMap<>();
 
-        // assign value to definedParams here
-        Map<String, Property> globalParams = 
parseGlobalParamsMap(workflowInstance);
-
-        // combining local and global parameters
-        Map<String, Property> localParams = 
parameters.getInputLocalParametersMap();
-
-        // stream pass params
-        List<Property> varPools = parseVarPool(taskInstance);
+        Map<String, Property> prepareParamsMap = new HashMap<>();
 
-        // if it is a complement,
-        // you need to pass in the task instance id to locate the time
-        // of the process instance complement
         ICommandParam commandParam = 
JSONUtils.parseObject(workflowInstance.getCommandParam(), ICommandParam.class);
-        String timeZone = commandParam.getTimeZone();
+        String timeZone = commandParam != null ? commandParam.getTimeZone() : 
null;

Review Comment:
   `commandParam` shouldn't be null.



##########
dolphinscheduler-service/src/main/java/org/apache/dolphinscheduler/service/expand/CuringParamsServiceImpl.java:
##########
@@ -160,107 +161,177 @@ public Map<String, Property> 
parseWorkflowFatherParam(@Nullable Map<String, Stri
     }
 
     /**
-     * Generate prepare params include project params, global parameters, 
local parameters, built-in parameters, varpool, start-up params.
-     * <p> The priority of the parameters is as follows:
-     * <p> varpool > command parameters > local parameters > global parameters 
> project parameters > built-in parameters
-     * todo: Use TaskRuntimeParams to represent this.
+     * Prepares the final map of task execution parameters by merging 
parameters from multiple sources
+     * in a well-defined priority order. The resulting map is guaranteed to 
contain only valid entries:
+     * <ul>
+     *   <li>Keys are non-null and non-blank strings</li>
+     *   <li>Values are non-null {@link Property} objects</li>
+     * </ul>
      *
-     * @param taskInstance
-     * @param parameters
-     * @param workflowInstance
-     * @param projectName
-     * @param workflowDefinitionName
-     * @return
+     * <p><strong>Parameter Precedence (highest to lowest):</strong>
+     * <ol>
+     *   <li>Business/scheduling time parameters (e.g., {@code 
${system.datetime}})</li>
+     *   <li>Command-line or runtime complement parameters</li>
+     *   <li>Task-local parameters</li>
+     *   <li>Workflow global parameters (solidified at instance creation)</li>
+     *   <li>Project-level parameters</li>
+     *   <li>Built-in system parameters (e.g., {@code ${task.id}})</li>
+     * </ol>
+     *
+     * <p><strong>Important Notes:</strong>
+     * <ul>
+     *   <li>All parameter sources are sanitized via {@link #safePutAll(Map, 
Map)} to prevent {@code null}
+     *       or blank keys, which would cause JSON serialization failures 
(e.g., Jackson's
+     *       "Null key for a Map not allowed in JSON").</li>
+     *   <li>Placeholders (e.g., {@code "${var}"}) in parameter values are 
resolved after all sources
+     *       are merged, using the consolidated parameter map. Global 
parameters are already
+     *       <em>solidified</em> (fully resolved at workflow instance 
creation), so no recursive
+     *       placeholder expansion is required.</li>
+     *   <li>{@code VarPool} values (from upstream tasks) only override 
parameters marked as
+     *       {@link Direct#IN}; output or constant parameters remain 
unchanged.</li>
+     * </ul>
+     *
+     * @param taskInstance           the current task instance (must not be 
null)
+     * @param parameters             the parsed task-specific parameters (must 
not be null)
+     * @param workflowInstance       the parent workflow instance (must not be 
null)
+     * @param projectName            name of the project containing the 
workflow
+     * @param workflowDefinitionName name of the workflow definition
+     * @return a safe, fully resolved map of parameter name to {@link 
Property}, ready for task execution
      */
     @Override
-    public Map<String, Property> paramParsingPreparation(@NonNull TaskInstance 
taskInstance,
+    public Map<String, Property> paramParsingPreparation(
+                                                         @NonNull TaskInstance 
taskInstance,
                                                          @NonNull 
AbstractParameters parameters,
                                                          @NonNull 
WorkflowInstance workflowInstance,
                                                          String projectName,
                                                          String 
workflowDefinitionName) {
-        Map<String, Property> prepareParamsMap = new HashMap<>();
 
-        // assign value to definedParams here
-        Map<String, Property> globalParams = 
parseGlobalParamsMap(workflowInstance);
-
-        // combining local and global parameters
-        Map<String, Property> localParams = 
parameters.getInputLocalParametersMap();
-
-        // stream pass params
-        List<Property> varPools = parseVarPool(taskInstance);
+        Map<String, Property> prepareParamsMap = new HashMap<>();
 
-        // if it is a complement,
-        // you need to pass in the task instance id to locate the time
-        // of the process instance complement
         ICommandParam commandParam = 
JSONUtils.parseObject(workflowInstance.getCommandParam(), ICommandParam.class);
-        String timeZone = commandParam.getTimeZone();
+        String timeZone = commandParam != null ? commandParam.getTimeZone() : 
null;
 
-        // built-in params
-        Map<String, String> builtInParams =
-                setBuiltInParamsMap(taskInstance, workflowInstance, timeZone, 
projectName, workflowDefinitionName);
+        // 1. Built-in parameters (lowest precedence)
+        Map<String, String> builtInParams = setBuiltInParamsMap(
+                taskInstance, workflowInstance, timeZone, projectName, 
workflowDefinitionName);
+        safePutAll(prepareParamsMap, 
ParameterUtils.getUserDefParamsMap(builtInParams));
 
-        // project-level params
+        // 2. Project-level parameters
         Map<String, Property> projectParams = 
getProjectParameterMap(taskInstance.getProjectCode());
+        safePutAll(prepareParamsMap, projectParams);
 
-        if (MapUtils.isNotEmpty(builtInParams)) {
-            
prepareParamsMap.putAll(ParameterUtils.getUserDefParamsMap(builtInParams));
-        }
-
-        if (MapUtils.isNotEmpty(projectParams)) {
-            prepareParamsMap.putAll(projectParams);
-        }
-
-        if (MapUtils.isNotEmpty(globalParams)) {
-            prepareParamsMap.putAll(globalParams);
-        }
-
-        if (MapUtils.isNotEmpty(localParams)) {
-            prepareParamsMap.putAll(localParams);
-        }
+        // 3. Workflow global parameters
+        Map<String, Property> globalParams = 
parseGlobalParamsMap(workflowInstance);
+        safePutAll(prepareParamsMap, globalParams);
 
-        if (CollectionUtils.isNotEmpty(commandParam.getCommandParams())) {
-            prepareParamsMap.putAll(commandParam.getCommandParams().stream()
-                    .collect(Collectors.toMap(Property::getProp, 
Function.identity())));
+        // 4. Task-local parameters
+        Map<String, Property> localParams = 
parameters.getInputLocalParametersMap();
+        safePutAll(prepareParamsMap, localParams);
+
+        // 5. Command-line / complement parameters
+        if (commandParam != null && 
CollectionUtils.isNotEmpty(commandParam.getCommandParams())) {
+            Map<String, Property> commandParamsMap = 
commandParam.getCommandParams().stream()
+                    .filter(prop -> StringUtils.isNotBlank(prop.getProp()))
+                    .collect(Collectors.toMap(
+                            Property::getProp,
+                            Function.identity(),
+                            (v1, v2) -> v2 // retain last on duplicate key
+                    ));
+            safePutAll(prepareParamsMap, commandParamsMap);
         }
 
+        // 6. VarPool: override values only for existing IN-direction 
parameters
+        List<Property> varPools = parseVarPool(taskInstance);
         if (CollectionUtils.isNotEmpty(varPools)) {
-            // overwrite the in parameter by varPool
             for (Property varPool : varPools) {
-                Property property = prepareParamsMap.get(varPool.getProp());
-                if (property == null || property.getDirect() != Direct.IN) {
+                if (StringUtils.isBlank(varPool.getProp())) {
                     continue;
                 }
-                property.setValue(varPool.getValue());
+                Property targetParam = prepareParamsMap.get(varPool.getProp());
+                if (targetParam != null && 
Direct.IN.equals(targetParam.getDirect())) {
+                    targetParam.setValue(varPool.getValue());
+                }
             }
         }
 
-        Iterator<Map.Entry<String, Property>> iter = 
prepareParamsMap.entrySet().iterator();
-        while (iter.hasNext()) {
-            Map.Entry<String, Property> en = iter.next();
-            Property property = en.getValue();
-
-            if (StringUtils.isNotEmpty(property.getValue())
-                    && 
property.getValue().contains(Constants.FUNCTION_START_WITH)) {
-                /**
-                 *  local parameter refers to global parameter with the same 
name
-                 *  note: the global parameters of the process instance here 
are solidified parameters,
-                 *  and there are no variables in them.
-                 */
-                String val = property.getValue();
-
-                // handle some chain parameter assign, such as `{"var1": 
"${var2}", "var2": 1}` should be convert to
-                // `{"var1": 1, "var2": 1}`
-                val = convertParameterPlaceholders(val, prepareParamsMap);
-                property.setValue(val);
-            }
+        // 7. Resolve placeholders (e.g., "${output_dir}") using the current 
parameter context
+        resolvePlaceholders(prepareParamsMap);

Review Comment:
   `preBuildBusinessParams` should before `resolvePlaceholders`?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to