Skip to main content
Version: Next

Asset Validation

This document summarises how asset validation is performed and how it relates to the asset type model.

AssetTypeInfo Model Structure


Constraint Validation Logic in ValueUtil.validateValue()

There are four independent sources of constraints, all evaluated cumulatively. A value is valid only if it passes every constraint from every source.

ValueUtil.validateValue(
attributeDescriptor, // static descriptor (from AssetTypeInfo)
valueDescriptor, // the value's type descriptor
metaHolder, // the live attribute instance (has its own MetaMap)
now, context, value
)

├─── [1] ValueDescriptor.constraints[] ← constraints on the VALUE TYPE itself
│ e.g. ValueType.NUMBER might have Min(0)
│ validateConstraints(arrayDimensions, constraints, ...)

├─── [2] AttributeDescriptor.constraints[] ← constraints on the ATTRIBUTE DESCRIPTOR
│ e.g. a specific attribute defined with .withConstraints(new Max(100))
│ validateConstraints(arrayDimensions, constraints, ...)

├─── [3] AttributeDescriptor.meta ← CONSTRAINTS meta on the DESCRIPTOR
│ attributeDescriptor.getMeta()
│ .get(MetaItemType.CONSTRAINTS) // MetaItemDescriptor<ValueConstraint[]>
│ .flatMap(ValueHolder::getValue) // Optional<ValueConstraint[]>
│ Each constraint → validateValueConstraint(...)

└─── [4] metaHolder.getMeta() ← CONSTRAINTS meta on the LIVE ATTRIBUTE
(the attribute instance itself, not the descriptor)
.get(MetaItemType.CONSTRAINTS)
Each constraint → validateValueConstraint(...)

Array value recursion (validateConstraints)

// ValueUtil.java:1043
private static boolean validateConstraints(Integer dimensions, ValueConstraint[] constraints, ..., Object value) {
if (dimensions == null || dimensions == 0 || value == null) {
// Leaf: evaluate each constraint directly against the value
return Arrays.stream(constraints)
.anyMatch(c -> !validateValueConstraint(..., c, value));
} else {
// Array: recurse into each element, decrementing dimension count
return Arrays.stream((Object[]) value)
.anyMatch(v -> validateConstraints(dimensions - 1, constraints, ..., v));
}
}

When valueDescriptor.arrayDimensions > 0, constraints are applied to each element, not the array itself (dimension is decremented on each recursion). Paths for error reporting are not updated during recursion, so violations always point to the attribute root.

validateValueConstraint (the leaf evaluator)

// ValueUtil.java:1051
public static boolean validateValueConstraint(..., ValueConstraint valueConstraint, Object value) {
if (!valueConstraint.evaluate(value, now)) {
// 1. Inject message parameters (min/max/pattern/etc.) into Hibernate context
// 2. Build violation with constraint's message template key
// 3. Apply the path provider to set the property path
return false; // invalid
}
return true; // valid
}

Constraint source summary

SourceWhere definedApplied via
ValueDescriptor.constraints[]On the value type (e.g. ValueType.NUMBER)validateConstraints() with array recursion
AttributeDescriptor.constraints[]On the static attribute definitionvalidateConstraints() with array recursion
AttributeDescriptor.meta[CONSTRAINTS]In the descriptor's MetaMap, key "constraints"Direct per-constraint loop
metaHolder.meta[CONSTRAINTS]On the live Attribute instance's MetaMapDirect per-constraint loop

The key distinction between sources 3 and 4 is that source 3 comes from the static descriptor (set by the developer defining the asset model), while source 4 comes from the live attribute (set at runtime, e.g. by a user configuring constraints via the UI or API). Both are checked with MetaItemType.CONSTRAINTS — the same MetaItemDescriptor<ValueConstraint[]>.