Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2024 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.maestro.models;

/**
* Static holder for configurable validation limits. Validators in maestro-common read from here
* instead of directly from {@link Constants}. At startup, the Spring layer calls {@link
* #initialize} to override the defaults with values from application config.
*/
public final class ValidationLimits {
Comment thread
jakhani marked this conversation as resolved.
Outdated
private ValidationLimits() {}

private static volatile int idLengthLimit = Constants.ID_LENGTH_LIMIT;
private static volatile int nameLengthLimit = Constants.NAME_LENGTH_LIMIT;

/** Returns the current ID length limit used by validators. */
public static int getIdLengthLimit() {
return idLengthLimit;
}

/** Returns the current name length limit used by validators. */
public static int getNameLengthLimit() {
return nameLengthLimit;
}

/**
* Overrides the default limits. Called once at application startup by the Spring configuration
* layer. Values must be positive integers.
*/
public static void initialize(int idLimit, int nameLimit) {
if (idLimit <= 0 || nameLimit <= 0) {
throw new IllegalArgumentException(
"Validation limits must be positive: idLengthLimit="
+ idLimit
+ ", nameLengthLimit="
+ nameLimit);
}
idLengthLimit = idLimit;
nameLengthLimit = nameLimit;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.netflix.maestro.models.parameter.ParamDefinition;
import com.netflix.maestro.models.signal.SignalDependenciesDefinition;
import com.netflix.maestro.models.signal.SignalOutputsDefinition;
import com.netflix.maestro.validations.MaestroNameSizeConstraint;
import com.netflix.maestro.validations.MaestroReferenceIdConstraint;
import com.netflix.maestro.validations.SignalDependenciesDefinitionConstraint;
import com.netflix.maestro.validations.SignalOutputsDefinitionConstraint;
Expand All @@ -44,7 +45,7 @@ public abstract class AbstractStep implements Step {
private String id;

@Getter(onMethod = @__({@Override}))
@Size(max = Constants.NAME_LENGTH_LIMIT)
@MaestroNameSizeConstraint
private String name;

@Getter(onMethod = @__({@Override}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.netflix.maestro.models.trigger.TimeTrigger;
import com.netflix.maestro.utils.MapHelper;
import com.netflix.maestro.validations.MaestroIdConstraint;
import com.netflix.maestro.validations.MaestroNameSizeConstraint;
import com.netflix.maestro.validations.SignalTriggerConstraint;
import com.netflix.maestro.validations.TagListConstraint;
import com.netflix.maestro.validations.TimeTriggerConstraint;
Expand Down Expand Up @@ -73,8 +74,7 @@ public class Workflow {
* Name of the workflow. Can be absent by user. Can be filled by workflow id if absent - use
* helper method in WorkflowHelper.
*/
@Size(max = Constants.NAME_LENGTH_LIMIT)
private final String name;
@MaestroNameSizeConstraint private final String name;

@Size(max = Constants.FIELD_SIZE_LIMIT)
private final String description;
Expand All @@ -96,7 +96,7 @@ public class Workflow {
@Builder(toBuilder = true)
Workflow(
String id,
@Size(max = Constants.NAME_LENGTH_LIMIT) String name,
String name,
@Size(max = Constants.FIELD_SIZE_LIMIT) String description,
@Valid TagList tags,
ParsableLong timeout,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package com.netflix.maestro.validations;

import com.netflix.maestro.models.Constants;
import com.netflix.maestro.models.ValidationLimits;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
Expand Down Expand Up @@ -56,12 +57,12 @@ public boolean isValid(String id, ConstraintValidatorContext context) {
return false;
}

if (id.length() > Constants.ID_LENGTH_LIMIT) {
if (id.length() > ValidationLimits.getIdLengthLimit()) {
context
.buildConstraintViolationWithTemplate(
String.format(
"[maestro id] cannot be more than id length limit %s - rejected length is [%s] for value [%s]",
Constants.ID_LENGTH_LIMIT, id.length(), id))
ValidationLimits.getIdLengthLimit(), id.length(), id))
.addConstraintViolation();
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package com.netflix.maestro.validations;

import com.netflix.maestro.models.Constants;
import com.netflix.maestro.models.ValidationLimits;
import com.netflix.maestro.utils.StringUtils;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
Expand Down Expand Up @@ -59,13 +60,13 @@ public boolean isValid(String id, ConstraintValidatorContext context) {
return false;
}

if (id.length() > Constants.NAME_LENGTH_LIMIT) {
if (id.length() > ValidationLimits.getNameLengthLimit()) {
context
.buildConstraintViolationWithTemplate(
String.format(
"[maestro name] cannot be more than name length limit %s "
+ "- rejected length is [%s] for value [%s]",
Constants.NAME_LENGTH_LIMIT, id.length(), id))
ValidationLimits.getNameLengthLimit(), id.length(), id))
.addConstraintViolation();
return false;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright 2024 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package com.netflix.maestro.validations;

import com.netflix.maestro.models.ValidationLimits;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Validates that a name field does not exceed the configurable name length limit. Unlike {@link
* MaestroNameConstraint}, this annotation only enforces length and does not apply regex or reserved
* prefix/suffix checks. It is null-safe (null is treated as absent and passes). It replaces
* {@code @Size(max = Constants.NAME_LENGTH_LIMIT)} on optional model name fields where the limit
* must be configurable at runtime.
*/
@Documented
@Constraint(validatedBy = MaestroNameSizeConstraint.MaestroNameSizeValidator.class)
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MaestroNameSizeConstraint {
/** input constraint message. */
String message() default "";

/** input constraint groups. */
Class<?>[] groups() default {};

/** input constraint payload. */
Class<? extends Payload>[] payload() default {};

/** Validates that the string length does not exceed the name length limit. */
class MaestroNameSizeValidator implements ConstraintValidator<MaestroNameSizeConstraint, String> {

@Override
public void initialize(MaestroNameSizeConstraint constraint) {}

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true; // null means absent; use @NotNull separately if the field is required
}
int limit = ValidationLimits.getNameLengthLimit();
if (value.length() > limit) {
context
.buildConstraintViolationWithTemplate(
String.format(
"[maestro name] cannot be more than name length limit %s"
+ " - rejected length is [%s] for value [%s]",
limit, value.length(), value))
.addConstraintViolation();
return false;
}
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
package com.netflix.maestro.validations;

import com.netflix.maestro.models.Constants;
import com.netflix.maestro.models.ValidationLimits;
import jakarta.validation.Constraint;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
Expand Down Expand Up @@ -58,13 +59,13 @@ public boolean isValid(String id, ConstraintValidatorContext context) {
return false;
}

if (id.length() > Constants.ID_LENGTH_LIMIT) {
if (id.length() > ValidationLimits.getIdLengthLimit()) {
context
.buildConstraintViolationWithTemplate(
String.format(
"[maestro id or name reference] cannot be more than id length limit %s "
+ "- rejected length is [%s] for value [%s]",
Constants.ID_LENGTH_LIMIT, id.length(), id))
ValidationLimits.getIdLengthLimit(), id.length(), id))
.addConstraintViolation();
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.junit.Assert.assertNull;

import com.netflix.maestro.models.Constants;
import com.netflix.maestro.models.ValidationLimits;
import jakarta.validation.ConstraintViolation;
import java.util.LinkedHashSet;
import java.util.Set;
Expand Down Expand Up @@ -67,12 +68,15 @@ public void isIdTooLong() {
new TestId(new String(new char[Constants.ID_LENGTH_LIMIT + 1]).replace("\0", "a")));
assertEquals(1, violations.size());
ConstraintViolation<TestId> violation = violations.iterator().next();
assertEquals(129, ((String) violation.getInvalidValue()).length());
assertEquals(
ValidationLimits.getIdLengthLimit() + 1, ((String) violation.getInvalidValue()).length());
assertEquals(
String.format(
"[maestro id] cannot be more than id length limit 128 "
"[maestro id] cannot be more than id length limit %s "
+ "- rejected length is [%s] for value [%s]",
129, new String(new char[Constants.ID_LENGTH_LIMIT + 1]).replace("\0", "a")),
ValidationLimits.getIdLengthLimit(),
ValidationLimits.getIdLengthLimit() + 1,
new String(new char[Constants.ID_LENGTH_LIMIT + 1]).replace("\0", "a")),
violation.getMessage());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.junit.Assert.assertNull;

import com.netflix.maestro.models.Constants;
import com.netflix.maestro.models.ValidationLimits;
import jakarta.validation.ConstraintViolation;
import java.util.LinkedHashSet;
import java.util.Set;
Expand Down Expand Up @@ -86,12 +87,15 @@ public void isNameTooLong() {
new TestName(new String(new char[Constants.NAME_LENGTH_LIMIT + 1]).replace("\0", "a")));
assertEquals(1, violations.size());
ConstraintViolation<TestName> violation = violations.iterator().next();
assertEquals(257, ((String) violation.getInvalidValue()).length());
assertEquals(
ValidationLimits.getNameLengthLimit() + 1, ((String) violation.getInvalidValue()).length());
assertEquals(
String.format(
"[maestro name] cannot be more than name length limit 256 "
"[maestro name] cannot be more than name length limit %s "
+ "- rejected length is [%s] for value [%s]",
257, new String(new char[Constants.NAME_LENGTH_LIMIT + 1]).replace("\0", "a")),
ValidationLimits.getNameLengthLimit(),
ValidationLimits.getNameLengthLimit() + 1,
new String(new char[Constants.NAME_LENGTH_LIMIT + 1]).replace("\0", "a")),
violation.getMessage());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import static org.junit.Assert.assertNull;

import com.netflix.maestro.models.Constants;
import com.netflix.maestro.models.ValidationLimits;
import jakarta.validation.ConstraintViolation;
import java.util.LinkedHashSet;
import java.util.Set;
Expand Down Expand Up @@ -62,12 +63,15 @@ public void isIdTooLong() {
new TestId(new String(new char[Constants.ID_LENGTH_LIMIT + 1]).replace("\0", "a")));
assertEquals(1, violations.size());
ConstraintViolation<TestId> violation = violations.iterator().next();
assertEquals(129, ((String) violation.getInvalidValue()).length());
assertEquals(
ValidationLimits.getIdLengthLimit() + 1, ((String) violation.getInvalidValue()).length());
assertEquals(
String.format(
"[maestro id or name reference] cannot be more than id length limit 128 "
"[maestro id or name reference] cannot be more than id length limit %s "
+ "- rejected length is [%s] for value [%s]",
129, new String(new char[Constants.ID_LENGTH_LIMIT + 1]).replace("\0", "a")),
ValidationLimits.getIdLengthLimit(),
ValidationLimits.getIdLengthLimit() + 1,
new String(new char[Constants.ID_LENGTH_LIMIT + 1]).replace("\0", "a")),
violation.getMessage());
}

Expand Down
Loading
Loading