UsersConfigRecord.java
package com.bonitasoft.processbuilder.records;
import com.fasterxml.jackson.databind.JsonNode;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
/**
* A record representing the user configuration for task assignation.
* <p>
* This record is parsed from a JSON structure with the following format:
* </p>
* <pre>{@code
* {
* "users": {
* "stepUser": "step_xxx",
* "stepManager": "step_yyy",
* "memberShips": ["membership_1", "membership_2"],
* "membersShipsInput": "step_zzz:field_www"
* }
* }
* }</pre>
* <p>
* All fields are nullable, allowing flexible configuration for different
* task assignation scenarios.
* </p>
*
* @param stepUser Reference to a step whose executor becomes a candidate (nullable)
* @param stepManager Reference to a step whose executor's manager becomes a candidate (nullable)
* @param memberShips Static list of membership references defined in BDM (nullable)
* @param membersShipsInput Dynamic membership reference in format "step_xxx:field_yyy" (nullable)
* @author Bonitasoft
* @since 1.0
*/
public record UsersConfigRecord(
String stepUser,
String stepManager,
List<String> memberShips,
String membersShipsInput
) {
/** JSON key for the users node. */
public static final String USERS_KEY = "users";
/** JSON key for stepUser. */
public static final String STEP_USER_KEY = "stepUser";
/** JSON key for stepManager. */
public static final String STEP_MANAGER_KEY = "stepManager";
/** JSON key for memberShips. */
public static final String MEMBERSHIPS_KEY = "memberShips";
/** JSON key for membersShipsInput. */
public static final String MEMBERSHIPS_INPUT_KEY = "membersShipsInput";
/**
* Compact constructor ensuring memberShips is immutable.
*
* @param stepUser The step user reference
* @param stepManager The step manager reference
* @param memberShips The list of membership references
* @param membersShipsInput The dynamic membership input reference
*/
public UsersConfigRecord {
memberShips = memberShips == null ? Collections.emptyList() : List.copyOf(memberShips);
}
/**
* Creates an empty UsersConfigRecord with all null/empty values.
*
* @return An empty UsersConfigRecord instance
*/
public static UsersConfigRecord empty() {
return new UsersConfigRecord(null, null, Collections.emptyList(), null);
}
/**
* Parses a "users" JsonNode and creates a UsersConfigRecord.
* <p>
* This method extracts all user configuration fields from the provided JsonNode.
* Missing or null fields result in null/empty values in the record.
* </p>
*
* @param usersNode The JsonNode containing the users configuration (nullable)
* @param logger Logger for debug messages (nullable)
* @return A UsersConfigRecord with parsed values, or empty record if usersNode is null
*/
public static UsersConfigRecord fromUsersNode(JsonNode usersNode, Logger logger) {
if (usersNode == null || usersNode.isNull()) {
logDebug(logger, "Users node is null, returning empty config");
return empty();
}
String stepUser = extractTextValue(usersNode, STEP_USER_KEY);
String stepManager = extractTextValue(usersNode, STEP_MANAGER_KEY);
List<String> memberShips = extractStringList(usersNode, MEMBERSHIPS_KEY);
String membersShipsInput = extractTextValue(usersNode, MEMBERSHIPS_INPUT_KEY);
logDebug(logger, "Parsed UsersConfigRecord: stepUser={}, stepManager={}, memberShips={}, membersShipsInput={}",
stepUser, stepManager, memberShips.size(), membersShipsInput);
return new UsersConfigRecord(stepUser, stepManager, memberShips, membersShipsInput);
}
/**
* Checks if this configuration has any step user reference.
*
* @return true if stepUser is not null and not blank
*/
public boolean hasStepUser() {
return stepUser != null && !stepUser.isBlank();
}
/**
* Checks if this configuration has any step manager reference.
*
* @return true if stepManager is not null and not blank
*/
public boolean hasStepManager() {
return stepManager != null && !stepManager.isBlank();
}
/**
* Checks if this configuration has any static memberships.
*
* @return true if memberShips is not empty
*/
public boolean hasMemberShips() {
return memberShips != null && !memberShips.isEmpty();
}
/**
* Checks if this configuration has a dynamic membership input reference.
*
* @return true if membersShipsInput is not null and not blank
*/
public boolean hasMembersShipsInput() {
return membersShipsInput != null && !membersShipsInput.isBlank();
}
/**
* Checks if this configuration has any user source defined.
*
* @return true if any of stepUser, stepManager, memberShips, or membersShipsInput is defined
*/
public boolean hasAnySource() {
return hasStepUser() || hasStepManager() || hasMemberShips() || hasMembersShipsInput();
}
/**
* Gets the stepUser as an Optional.
*
* @return Optional containing the stepUser, or empty if not defined
*/
public Optional<String> getStepUserOptional() {
return hasStepUser() ? Optional.of(stepUser) : Optional.empty();
}
/**
* Gets the stepManager as an Optional.
*
* @return Optional containing the stepManager, or empty if not defined
*/
public Optional<String> getStepManagerOptional() {
return hasStepManager() ? Optional.of(stepManager) : Optional.empty();
}
/**
* Gets the membersShipsInput as an Optional.
*
* @return Optional containing the membersShipsInput, or empty if not defined
*/
public Optional<String> getMembersShipsInputOptional() {
return hasMembersShipsInput() ? Optional.of(membersShipsInput) : Optional.empty();
}
/**
* Parses the membersShipsInput into a StepFieldRef.
*
* @return Optional containing the parsed StepFieldRef, or empty if not defined or invalid format
*/
public Optional<StepFieldRef> parseMembersShipsInput() {
if (!hasMembersShipsInput()) {
return Optional.empty();
}
return Optional.ofNullable(StepFieldRef.parse(membersShipsInput));
}
// ========================================================================
// Private Helper Methods
// ========================================================================
private static String extractTextValue(JsonNode parentNode, String key) {
JsonNode node = parentNode.get(key);
if (node == null || node.isNull()) {
return null;
}
String text = node.asText();
return (text == null || text.isBlank()) ? null : text.trim();
}
private static List<String> extractStringList(JsonNode parentNode, String key) {
JsonNode arrayNode = parentNode.get(key);
if (arrayNode == null || !arrayNode.isArray() || arrayNode.isEmpty()) {
return Collections.emptyList();
}
List<String> result = new ArrayList<>(arrayNode.size());
arrayNode.forEach(node -> {
String text = node.asText();
if (text != null && !text.isBlank()) {
result.add(text.trim());
}
});
return result;
}
private static void logDebug(Logger logger, String message, Object... args) {
if (logger != null && logger.isDebugEnabled()) {
logger.debug(message, args);
}
}
}