JsonValidationUtils.java

package com.bonitasoft.processbuilder.extension;

import com.fasterxml.jackson.databind.JsonNode;
import org.bonitasoft.engine.connector.ConnectorValidationException;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.StreamSupport;

/**
 * Utility class for generic JSON structure validation checks, decoupled from the main parsing logic.
 */
public final class JsonValidationUtils {

    /**
     * Private constructor to prevent instantiation of this utility class.
     *
     * @throws UnsupportedOperationException always, to enforce the utility pattern.
     */
    private JsonValidationUtils() {
        throw new UnsupportedOperationException("This is a " + this.getClass().getSimpleName() + " class and cannot be instantiated.");
    }

    /**
     * A utility method to validate the existence and type of a specific field in a JSON node.
     * @param parentNode The parent JSON node to search in.
     * @param fieldName The name of the field to validate.
     * @param typeCheck A predicate to check the required type of the field (e.g., JsonNode::isTextual).
     * @param errorMessageField The name of the field to use in the error message.
     * @throws ConnectorValidationException if the field is missing or has an invalid type.
     */
    public static void validateField(
            final JsonNode parentNode,
            final String fieldName,
            final Predicate<JsonNode> typeCheck,
            final String errorMessageField) throws ConnectorValidationException {
        JsonNode fieldNode = parentNode.get(fieldName);

        Optional.ofNullable(fieldNode)
            .filter(typeCheck)
            .orElseThrow(() -> new ConnectorValidationException(
                String.format("Mandatory field '%s' is missing or has an invalid type.", errorMessageField)));
    }

    /**
     * Validates that the 'memberShips' node is a JSON array and that all elements 
     * within the array are non-empty strings (membership references).
     * @param memberShipsNode The JSON node containing the list of membership references.
     * @throws ConnectorValidationException if the node is not an array or if any element is not a valid string reference.
     */
    public static void validateMemberships(final JsonNode memberShipsNode) throws ConnectorValidationException {
        
        // 1. Validate that the main node is an Array.
        if (memberShipsNode == null || !memberShipsNode.isArray()) {
             // We check for null explicitly to provide a more specific message if needed, but array check usually suffices
             throw new ConnectorValidationException("The 'memberShips' configuration must be a JSON array.");
        }

        try {
            StreamSupport.stream(memberShipsNode.spliterator(), false)
                .forEach(referenceNode -> {
                    
                    // 2. Validate that each element is a string (textual node).
                    if (!referenceNode.isTextual()) {
                        throw new RuntimeException("Each element in the 'memberShips' array must be a string (membership reference).");
                    }
                    
                    // 3. Validate that the string is not empty.
                    if (referenceNode.asText().trim().isEmpty()) {
                        throw new RuntimeException("Membership references in the array cannot be empty strings.");
                    }
                });
        } catch (RuntimeException e) {
            // 4. Catch the exception thrown in the stream and re-throw it as a ConnectorValidationException.
            // This is necessary because lambdas cannot throw checked exceptions.
            throw new ConnectorValidationException(e.getMessage());
        }
    }
}