Compare commits
2 Commits
c2cd6c2f23
...
32a8321c0e
Author | SHA1 | Date | |
---|---|---|---|
|
32a8321c0e | ||
|
79c7469fef |
@ -127,12 +127,14 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
|||||||
|
|
||||||
new HsHostingAssetEntityPatcher(em, current).apply(body);
|
new HsHostingAssetEntityPatcher(em, current).apply(body);
|
||||||
|
|
||||||
|
// TODO.refa: draft for an alternative API
|
||||||
// validate(current) // self-validation, hashing passwords etc.
|
// validate(current) // self-validation, hashing passwords etc.
|
||||||
// .then(HsHostingAssetEntityValidatorRegistry::prepareForSave) // hashing passwords etc.
|
// .then(HsHostingAssetEntityValidatorRegistry::prepareForSave) // hashing passwords etc.
|
||||||
// .then(assetRepo::save)
|
// .then(assetRepo::save)
|
||||||
// .then(HsHostingAssetEntityValidatorRegistry::validateInContext)
|
// .then(HsHostingAssetEntityValidatorRegistry::validateInContext)
|
||||||
// .then(this::mapToResource) using postprocessProperties to remove write-only + add read-only properties
|
// // In this last step we need the entity and the mapped resource instance,
|
||||||
|
// // which is exactly what a postmapper takes as arguments.
|
||||||
|
// .then(this::mapToResource) using postProcessProperties to remove write-only + add read-only properties
|
||||||
|
|
||||||
final var saved = validated(assetRepo.save(current));
|
final var saved = validated(assetRepo.save(current));
|
||||||
final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
final var mapped = mapper.map(saved, HsHostingAssetResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||||
|
@ -52,7 +52,7 @@ public class HsHostingAssetEntityValidatorRegistry {
|
|||||||
|
|
||||||
public static void postprocessProperties(final HsHostingAssetEntity entity, final HsHostingAssetResource resource) {
|
public static void postprocessProperties(final HsHostingAssetEntity entity, final HsHostingAssetResource resource) {
|
||||||
final var validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType());
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(entity.getType());
|
||||||
final var config = validator.postprocess(entity, asMap(resource));
|
final var config = validator.postProcess(entity, asMap(resource));
|
||||||
resource.setConfig(config);
|
resource.setConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@ public abstract class HsEntityValidator<E extends PropertiesProvider> {
|
|||||||
throw new IllegalArgumentException("Integer value (or null) expected, but got " + value);
|
throw new IllegalArgumentException("Integer value (or null) expected, but got " + value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Object> postprocess(final E entity, final Map<String, Object> config) {
|
public Map<String, Object> postProcess(final E entity, final Map<String, Object> config) {
|
||||||
final var copy = new HashMap<>(config);
|
final var copy = new HashMap<>(config);
|
||||||
stream(propertyValidators).forEach(p -> {
|
stream(propertyValidators).forEach(p -> {
|
||||||
if ( p.writeOnly) {
|
if ( p.writeOnly) {
|
||||||
|
@ -11,7 +11,7 @@ public class IntegerProperty extends ValidatableProperty<Integer> {
|
|||||||
|
|
||||||
private final static String[] KEY_ORDER = Array.join(
|
private final static String[] KEY_ORDER = Array.join(
|
||||||
ValidatableProperty.KEY_ORDER_HEAD,
|
ValidatableProperty.KEY_ORDER_HEAD,
|
||||||
Array.of("unit", "min", "max", "step"),
|
Array.of("unit", "min", "minFrom", "max", "maxFrom", "step"),
|
||||||
ValidatableProperty.KEY_ORDER_TAIL);
|
ValidatableProperty.KEY_ORDER_TAIL);
|
||||||
|
|
||||||
private String unit;
|
private String unit;
|
||||||
|
@ -12,9 +12,10 @@ public class StringProperty extends ValidatableProperty<String> {
|
|||||||
|
|
||||||
private static final String[] KEY_ORDER = Array.join(
|
private static final String[] KEY_ORDER = Array.join(
|
||||||
ValidatableProperty.KEY_ORDER_HEAD,
|
ValidatableProperty.KEY_ORDER_HEAD,
|
||||||
Array.of("values"),
|
Array.of("matchesRegEx", "minLength", "maxLength"),
|
||||||
ValidatableProperty.KEY_ORDER_TAIL);
|
ValidatableProperty.KEY_ORDER_TAIL,
|
||||||
private Pattern regExPattern;
|
Array.of("hidden"));
|
||||||
|
private Pattern matchesRegEx;
|
||||||
private Integer minLength;
|
private Integer minLength;
|
||||||
private Integer maxLength;
|
private Integer maxLength;
|
||||||
private boolean hidden;
|
private boolean hidden;
|
||||||
@ -38,7 +39,7 @@ public class StringProperty extends ValidatableProperty<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public StringProperty matchesRegEx(final String regExPattern) {
|
public StringProperty matchesRegEx(final String regExPattern) {
|
||||||
this.regExPattern = Pattern.compile(regExPattern);
|
this.matchesRegEx = Pattern.compile(regExPattern);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,8 +56,8 @@ public class StringProperty extends ValidatableProperty<String> {
|
|||||||
if (maxLength != null && propValue.length()>maxLength) {
|
if (maxLength != null && propValue.length()>maxLength) {
|
||||||
result.add(propertyName + "' length is expected to be at max " + maxLength + " but length of " + display(propValue) + " is " + propValue.length());
|
result.add(propertyName + "' length is expected to be at max " + maxLength + " but length of " + display(propValue) + " is " + propValue.length());
|
||||||
}
|
}
|
||||||
if (regExPattern != null && !regExPattern.matcher(propValue).matches()) {
|
if (matchesRegEx != null && !matchesRegEx.matcher(propValue).matches()) {
|
||||||
result.add(propertyName + "' is expected to be match " + regExPattern + " but " + display(propValue) + " does not match");
|
result.add(propertyName + "' is expected to be match " + matchesRegEx + " but " + display(propValue) + " does not match");
|
||||||
}
|
}
|
||||||
if (readOnly && propValue != null) {
|
if (readOnly && propValue != null) {
|
||||||
result.add(propertyName + "' is readonly but given as " + display(propValue));
|
result.add(propertyName + "' is readonly but given as " + display(propValue));
|
||||||
|
@ -25,7 +25,7 @@ import static java.util.Optional.ofNullable;
|
|||||||
public abstract class ValidatableProperty<T> {
|
public abstract class ValidatableProperty<T> {
|
||||||
|
|
||||||
protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName");
|
protected static final String[] KEY_ORDER_HEAD = Array.of("propertyName");
|
||||||
protected static final String[] KEY_ORDER_TAIL = Array.of("required", "defaultValue", "isTotalsValidator", "thresholdPercentage");
|
protected static final String[] KEY_ORDER_TAIL = Array.of("required", "defaultValue", "readOnly", "writeOnly", "computed", "isTotalsValidator", "thresholdPercentage");
|
||||||
|
|
||||||
final Class<T> type;
|
final Class<T> type;
|
||||||
final String propertyName;
|
final String propertyName;
|
||||||
@ -33,6 +33,7 @@ public abstract class ValidatableProperty<T> {
|
|||||||
private Boolean required;
|
private Boolean required;
|
||||||
private T defaultValue;
|
private T defaultValue;
|
||||||
protected Function<PropertiesProvider, T> computedBy;
|
protected Function<PropertiesProvider, T> computedBy;
|
||||||
|
protected boolean computed; // used in descriptor, because computedBy cannot be rendered to a text string
|
||||||
protected boolean readOnly;
|
protected boolean readOnly;
|
||||||
protected boolean writeOnly;
|
protected boolean writeOnly;
|
||||||
|
|
||||||
@ -177,26 +178,32 @@ public abstract class ValidatableProperty<T> {
|
|||||||
// Add entries according to the given order
|
// Add entries according to the given order
|
||||||
for (String key : keyOrder) {
|
for (String key : keyOrder) {
|
||||||
final Optional<Object> propValue = getPropertyValue(key);
|
final Optional<Object> propValue = getPropertyValue(key);
|
||||||
propValue.ifPresent(o -> sortedMap.put(key, o));
|
propValue.filter(ValidatableProperty::isToBeRendered).ifPresent(o -> sortedMap.put(key, o));
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortedMap;
|
return sortedMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isToBeRendered(final Object v) {
|
||||||
|
return !(v instanceof Boolean b) || b;
|
||||||
|
}
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
private Optional<Object> getPropertyValue(final String key) {
|
private Optional<Object> getPropertyValue(final String key) {
|
||||||
try {
|
return getPropertyValue(getClass(), key);
|
||||||
final var field = getClass().getDeclaredField(key);
|
|
||||||
field.setAccessible(true);
|
|
||||||
return Optional.ofNullable(arrayToList(field.get(this)));
|
|
||||||
} catch (final NoSuchFieldException e1) {
|
|
||||||
try {
|
|
||||||
final var field = getClass().getSuperclass().getDeclaredField(key);
|
|
||||||
field.setAccessible(true);
|
|
||||||
return Optional.ofNullable(arrayToList(field.get(this)));
|
|
||||||
} catch (final NoSuchFieldException e2) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SneakyThrows
|
||||||
|
private Optional<Object> getPropertyValue(final Class<?> clazz, final String key) {
|
||||||
|
try {
|
||||||
|
final var field = clazz.getDeclaredField(key);
|
||||||
|
field.setAccessible(true);
|
||||||
|
return Optional.ofNullable(arrayToList(field.get(this)));
|
||||||
|
} catch (final NoSuchFieldException exc) {
|
||||||
|
if (clazz.getSuperclass() != null) {
|
||||||
|
return getPropertyValue(clazz.getSuperclass(), key);
|
||||||
|
}
|
||||||
|
throw exc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,6 +227,7 @@ public abstract class ValidatableProperty<T> {
|
|||||||
|
|
||||||
public ValidatableProperty<T> computedBy(final Function<PropertiesProvider, T> compute) {
|
public ValidatableProperty<T> computedBy(final Function<PropertiesProvider, T> compute) {
|
||||||
this.computedBy = compute;
|
this.computedBy = compute;
|
||||||
|
this.computed = true;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,4 +107,25 @@ class HsUnixUserHostingAssetValidatorUnitTest {
|
|||||||
assertThat(result).containsExactly(
|
assertThat(result).containsExactly(
|
||||||
"'identifier' expected to match '^abc00$|^abc00-[a-z0-9]+$', but is 'xyz99-temp'");
|
"'identifier' expected to match '^abc00$|^abc00-[a-z0-9]+$', but is 'xyz99-temp'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void describesItsProperties() {
|
||||||
|
// given
|
||||||
|
final var validator = HsHostingAssetEntityValidatorRegistry.forType(UNIX_USER);
|
||||||
|
|
||||||
|
// when
|
||||||
|
final var props = validator.properties();
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(props).extracting(Object::toString).containsExactlyInAnyOrder(
|
||||||
|
"{type=integer, propertyName=SSD hard quota, unit=GB, maxFrom=SSD}",
|
||||||
|
"{type=integer, propertyName=SSD soft quota, unit=GB, maxFrom=SSD hard quota}",
|
||||||
|
"{type=integer, propertyName=HDD hard quota, unit=GB, maxFrom=HDD}",
|
||||||
|
"{type=integer, propertyName=HDD soft quota, unit=GB, maxFrom=HDD hard quota}",
|
||||||
|
"{type=enumeration, propertyName=shell, values=[/bin/false, /bin/bash, /bin/csh, /bin/dash, /usr/bin/tcsh, /usr/bin/zsh, /usr/bin/passwd], defaultValue=/bin/false}",
|
||||||
|
"{type=string, propertyName=homedir, readOnly=true, computed=true}",
|
||||||
|
"{type=string, propertyName=totpKey, matchesRegEx=^0x([0-9A-Fa-f]{2})+$, minLength=20, maxLength=256, writeOnly=true, hidden=true}",
|
||||||
|
"{type=password, propertyName=password, minLength=8, maxLength=40, writeOnly=true, hidden=true}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user