InputValidationUtils.java
package com.bonitasoft.processbuilder.extension;
import org.bonitasoft.engine.connector.ConnectorValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Optional;
/**
* Utility class for common input parameter validation checks (e.g., positive numbers, non-null values)
* typically used within Bonita connectors.
* <p>
* NOTE: This utility assumes the calling class has an accessible 'getInputParameter(String name)' method.
* </p>
*/
public final class InputValidationUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(InputValidationUtils.class);
/**
* Private constructor to prevent instantiation of this utility class.
*
* @throws UnsupportedOperationException always, to enforce the utility pattern.
*/
private InputValidationUtils() {
throw new UnsupportedOperationException("This is a " + this.getClass().getSimpleName() + " class and cannot be instantiated.");
}
/**
* Parses a String input to a positive Long value with comprehensive validation and logging.
* <p>
* This method handles various edge cases for String-to-Long conversion:
* </p>
* <ul>
* <li>If input is {@code null} → returns {@code Optional.empty()}</li>
* <li>If trimmed input is empty or equals "null" (case-insensitive) → returns {@code Optional.empty()}</li>
* <li>If parsing fails (NumberFormatException) → returns {@code Optional.empty()}</li>
* <li>If parsed value is not positive (≤ 0) → returns {@code Optional.empty()}</li>
* <li>If valid positive Long → returns {@code Optional.of(value)}</li>
* </ul>
* <p>
* All validation failures are logged with appropriate level (debug for null/empty, warn for invalid values).
* </p>
*
* @param input the String value to parse (may be null)
* @param paramName the parameter name for logging purposes (used in log messages)
* @return an {@code Optional<Long>} containing the positive Long value if valid,
* or {@code Optional.empty()} if the input is null, empty, "null", unparseable, or not positive
*/
public static Optional<Long> parseStringToPositiveLong(String input, String paramName) {
// Handle null input
if (input == null) {
LOGGER.debug("Input '{}' is null.", paramName);
return Optional.empty();
}
String trimmedInput = input.trim();
// Handle empty or "null" string
if (trimmedInput.isEmpty() || "null".equalsIgnoreCase(trimmedInput)) {
LOGGER.debug("Input '{}' value '{}' treated as null or empty.", paramName, trimmedInput);
return Optional.empty();
}
// Attempt to parse to Long
Long parsedValue;
try {
parsedValue = Long.parseLong(trimmedInput);
} catch (NumberFormatException e) {
LOGGER.warn("Error converting '{}' value '{}' to Long. Invalid number format.", paramName, trimmedInput);
return Optional.empty();
}
// Validate positive value
if (parsedValue <= 0L) {
LOGGER.warn("Invalid input: '{}' must be positive but received: {}", paramName, parsedValue);
return Optional.empty();
}
LOGGER.debug("Successfully parsed '{}' to positive Long: {}", paramName, parsedValue);
return Optional.of(parsedValue);
}
/**
* Parses a String input to a Long value with comprehensive validation and logging.
* <p>
* This method handles various edge cases for String-to-Long conversion:
* </p>
* <ul>
* <li>If input is {@code null} → returns {@code null}</li>
* <li>If trimmed input is empty or equals "null" (case-insensitive) → returns {@code null}</li>
* <li>If parsing fails (NumberFormatException) → returns {@code 0L}</li>
* <li>If valid number → returns the parsed Long value</li>
* </ul>
* <p>
* All validation failures are logged with appropriate level (debug for null/empty, error for parse failures).
* </p>
*
* <p><b>Usage Example:</b></p>
* <pre>{@code
* Long actionIdLong = InputValidationUtils.parseStringToLong(actionPersistenceIdInput, "actionPersistenceIdInput");
* if (actionIdLong == null || actionIdLong <= 0L) {
* logger.warn("Invalid input: '{}' is null or not positive.", "actionPersistenceIdInput");
* return Collections.emptyList();
* }
* }</pre>
*
* @param input the String value to parse (may be null)
* @param paramName the parameter name for logging purposes (used in log messages)
* @return the parsed Long value, {@code null} if input is null/empty/"null",
* or {@code 0L} if the input cannot be parsed as a number
*/
public static Long parseStringToLong(String input, String paramName) {
// Handle null input
if (input == null) {
LOGGER.debug("Input '{}' is null.", paramName);
return null;
}
String trimmedInput = input.trim();
// Handle empty or "null" string
if (trimmedInput.isEmpty() || "null".equalsIgnoreCase(trimmedInput)) {
LOGGER.debug("Input '{}' value '{}' treated as null or empty.", paramName, trimmedInput);
return null;
}
// Attempt to parse to Long
try {
Long parsedValue = Long.parseLong(trimmedInput);
LOGGER.debug("Successfully parsed '{}' to Long: {}", paramName, parsedValue);
return parsedValue;
} catch (NumberFormatException e) {
LOGGER.error("Error converting '{}' value '{}' to Long. Treating as invalid ID.", paramName, trimmedInput, e);
return 0L;
}
}
/**
* Helper method to check if a numeric input parameter is a positive value (greater than zero).
* <p>
* This method leverages the {@code doubleValue()} method available on all {@code Number} subclasses
* for a safe, consistent numerical comparison against zero, regardless of whether T is
* Integer, Long, Double, etc.
* </p>
* * @param <T> The generic numeric type, constrained to extend {@code Number} and {@code Comparable}.
* @param inputName The display name of the input parameter (used in error messages).
* @param value The numeric value of the input parameter retrieved from the connector context.
* @param typeName The simple name of the expected type (e.g., "integer", "long") for error messages.
* @throws ConnectorValidationException if the {@code value} is {@code null} or less than or equal to zero.
*/
private static <T extends Number & Comparable<T>> void checkPositiveNumber(final String inputName, final T value, final String typeName) throws ConnectorValidationException {
if (value == null || value.doubleValue() <= 0) {
throw new ConnectorValidationException(String.format("Mandatory parameter '%s' must be a positive %s but is '%s'.", inputName, typeName, value));
}
}
/**
* Checks if a given input parameter is a positive Integer.
* @param inputName The name of the input parameter.
* @param inputGetter A functional interface (Supplier) to retrieve the input parameter value.
* @throws ConnectorValidationException if the parameter is not a positive Integer.
*/
public static void checkPositiveIntegerInput(
final String inputName,
final java.util.function.Supplier<Object> inputGetter) throws ConnectorValidationException {
try {
Integer value = (Integer) inputGetter.get();
checkPositiveNumber(inputName, value, "integer");
} catch (ClassCastException e) {
throw new ConnectorValidationException(String.format("'%s' parameter must be an Integer", inputName));
}
}
/**
* Checks if a given input parameter is a positive Long.
* @param inputName The name of the input parameter.
* @param inputGetter A functional interface (Supplier) to retrieve the input parameter value.
* @throws ConnectorValidationException if the parameter is not a positive Long.
*/
public static void checkPositiveLongInput(
final String inputName,
final java.util.function.Supplier<Object> inputGetter) throws ConnectorValidationException {
try {
Long value = (Long) inputGetter.get();
checkPositiveNumber(inputName, value, "long");
} catch (ClassCastException e) {
throw new ConnectorValidationException(String.format("'%s' parameter must be an Long", inputName));
}
}
}