DSL for hierarchical validation for booking-items and improved violation error messages
This commit is contained in:
parent
eb98ab99be
commit
1d2a65ac22
@ -17,7 +17,7 @@ import java.util.List;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.valid;
|
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.valid;
|
||||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
|
@ -10,7 +10,6 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||||
import net.hostsharing.hsadminng.hs.validation.Validatable;
|
|
||||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||||
@ -65,7 +64,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
|||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatable<HsBookingItemEntity, HsBookingItemType> {
|
public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
||||||
|
|
||||||
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
|
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
|
||||||
.withProp(HsBookingItemEntity::getProject)
|
.withProp(HsBookingItemEntity::getProject)
|
||||||
@ -157,16 +156,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
|
|||||||
return parentItem == null ? null : parentItem.relatedProject();
|
return parentItem == null ? null : parentItem.relatedProject();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPropertiesName() {
|
|
||||||
return "resources";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getProperties() {
|
|
||||||
return resources;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HsBookingProjectEntity getRelatedProject() {
|
public HsBookingProjectEntity getRelatedProject() {
|
||||||
return project != null ? project : parentItem.getRelatedProject();
|
return project != null ? project : parentItem.getRelatedProject();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,120 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||||
|
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||||
|
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
|
||||||
|
|
||||||
|
import jakarta.validation.ValidationException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static java.util.Arrays.stream;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Optional.ofNullable;
|
||||||
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
|
||||||
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
|
||||||
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
|
||||||
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
|
||||||
|
|
||||||
|
public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingItemEntity> {
|
||||||
|
|
||||||
|
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity>> validators = new HashMap<>();
|
||||||
|
static {
|
||||||
|
register(PRIVATE_CLOUD, new HsPrivateCloudBookingItemValidator());
|
||||||
|
register(CLOUD_SERVER, new HsCloudServerBookingItemValidator());
|
||||||
|
register(MANAGED_SERVER, new HsManagedServerBookingItemValidator());
|
||||||
|
register(MANAGED_WEBSPACE, new HsManagedWebspaceBookingItemValidator());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItemEntity> validator) {
|
||||||
|
stream(validator.propertyValidators).forEach( entry -> {
|
||||||
|
entry.verifyConsistency(Map.entry(type, validator));
|
||||||
|
});
|
||||||
|
validators.put(type, validator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HsEntityValidator<HsBookingItemEntity> forType(final Enum<HsBookingItemType> type) {
|
||||||
|
if ( validators.containsKey(type)) {
|
||||||
|
return validators.get(type);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("no validator found for type " + type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<Enum<HsBookingItemType>> types() {
|
||||||
|
return validators.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) {
|
||||||
|
final var violations = doValidate(entityToSave);
|
||||||
|
if (!violations.isEmpty()) {
|
||||||
|
throw new ValidationException(violations.toString());
|
||||||
|
}
|
||||||
|
return entityToSave;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
|
||||||
|
return HsBookingItemEntityValidator.forType(bookingItem.getType()).validate(bookingItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HsBookingItemEntityValidator(final ValidatableProperty<?>... properties) {
|
||||||
|
super(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> validate(final HsBookingItemEntity bookingItem) {
|
||||||
|
return sequentiallyValidate(
|
||||||
|
() -> enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources())),
|
||||||
|
() -> enrich(prefix(bookingItem.toShortString(), "parentItem"), optionallyValidate(bookingItem.getParentItem())),
|
||||||
|
() -> validateSubEntities(bookingItem)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
|
||||||
|
return bookingItem != null ? HsBookingItemEntityValidator.doValidate(bookingItem) : emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<String> validateSubEntities(final HsBookingItemEntity bookingItem) {
|
||||||
|
return stream(propertyValidators)
|
||||||
|
.filter(propDef -> propDef.isTotalsValidator())
|
||||||
|
.map(prop -> validateMaxTotalValue(bookingItem, prop))
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
private List<String> sequentiallyValidate(final Supplier<List<String>>... validators) {
|
||||||
|
return stream(validators)
|
||||||
|
.map(Supplier::get)
|
||||||
|
.filter(violations -> !violations.isEmpty())
|
||||||
|
.findFirst()
|
||||||
|
.orElse(emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String validateMaxTotalValue(
|
||||||
|
final HsBookingItemEntity bookingItem,
|
||||||
|
final ValidatableProperty<?> propDef) {
|
||||||
|
final var propName = propDef.propertyName();
|
||||||
|
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");
|
||||||
|
final var totalValue = ofNullable(bookingItem.getSubBookingItems()).orElse(emptyList())
|
||||||
|
.stream()
|
||||||
|
.map(subItem -> propDef.getValue(subItem.getResources()))
|
||||||
|
.map(HsBookingItemEntityValidator::toInteger)
|
||||||
|
.reduce(0, Integer::sum);
|
||||||
|
final var maxValue = toInteger(propDef.getValue(bookingItem.getResources()));
|
||||||
|
return totalValue > maxValue
|
||||||
|
? "total %s is %d%s exceeds max total %s %d%s".formatted(
|
||||||
|
propName, totalValue, propUnit, propName, maxValue, propUnit)
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Integer toInteger(final Object value) {
|
||||||
|
if (value instanceof Integer) {
|
||||||
|
return (Integer) value;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Integer value expected, but got " + value);
|
||||||
|
}
|
||||||
|
}
|
@ -1,57 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
|
||||||
|
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
|
||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
|
||||||
|
|
||||||
import jakarta.validation.ValidationException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static java.util.Arrays.stream;
|
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
|
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
|
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
|
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
|
|
||||||
|
|
||||||
@UtilityClass
|
|
||||||
public class HsBookingItemEntityValidators {
|
|
||||||
|
|
||||||
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity, HsBookingItemType>> validators = new HashMap<>();
|
|
||||||
static {
|
|
||||||
register(PRIVATE_CLOUD, new HsPrivateCloudBookingItemValidator());
|
|
||||||
register(CLOUD_SERVER, new HsCloudServerBookingItemValidator());
|
|
||||||
register(MANAGED_SERVER, new HsManagedServerBookingItemValidator());
|
|
||||||
register(MANAGED_WEBSPACE, new HsManagedWebspaceBookingItemValidator());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void register(final Enum<HsBookingItemType> type, final HsEntityValidator<HsBookingItemEntity, HsBookingItemType> validator) {
|
|
||||||
stream(validator.propertyValidators).forEach( entry -> {
|
|
||||||
entry.verifyConsistency(Map.entry(type, validator));
|
|
||||||
});
|
|
||||||
validators.put(type, validator);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HsEntityValidator<HsBookingItemEntity, HsBookingItemType> forType(final Enum<HsBookingItemType> type) {
|
|
||||||
return validators.get(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Set<Enum<HsBookingItemType>> types() {
|
|
||||||
return validators.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HsBookingItemEntity valid(final HsBookingItemEntity entityToSave) {
|
|
||||||
final var violations = validate(entityToSave);
|
|
||||||
if (!violations.isEmpty()) {
|
|
||||||
throw new ValidationException(violations.toString());
|
|
||||||
}
|
|
||||||
return entityToSave;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<String> validate(final HsBookingItemEntity bookingItem) {
|
|
||||||
return HsBookingItemEntityValidators.forType(bookingItem.getType()).validate(bookingItem);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +1,13 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
|
||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
|
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
|
||||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||||
|
|
||||||
class HsCloudServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
|
class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator {
|
||||||
|
|
||||||
HsCloudServerBookingItemValidator() {
|
HsCloudServerBookingItemValidator() {
|
||||||
super(
|
super(
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
|
||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty;
|
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
|
||||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
|
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
|
||||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||||
|
|
||||||
class HsManagedServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
|
class HsManagedServerBookingItemValidator extends HsBookingItemEntityValidator {
|
||||||
|
|
||||||
HsManagedServerBookingItemValidator() {
|
HsManagedServerBookingItemValidator() {
|
||||||
super(
|
super(
|
||||||
|
@ -1,24 +1,47 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import java.util.List;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
|
||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
|
||||||
|
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static net.hostsharing.hsadminng.hs.validation.BooleanProperty.booleanProperty;
|
||||||
|
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
|
||||||
|
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty;
|
class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator {
|
||||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
|
|
||||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
|
||||||
|
|
||||||
class HsManagedWebspaceBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
|
|
||||||
|
|
||||||
public HsManagedWebspaceBookingItemValidator() {
|
public HsManagedWebspaceBookingItemValidator() {
|
||||||
super(
|
super(
|
||||||
integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(),
|
integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(),
|
||||||
integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(),
|
integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(),
|
||||||
integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(),
|
integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(),
|
||||||
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").optional(),
|
integerProperty("MultiOptions").min(1).max(100).step(1).required()
|
||||||
|
.asTotalLimitFor( 25, HsManagedWebspaceBookingItemValidator::totalUnixUsers)
|
||||||
|
.asTotalLimitFor( 5, HsManagedWebspaceBookingItemValidator::totalDatabaseUsers)
|
||||||
|
.asTotalLimitFor( 5, HsManagedWebspaceBookingItemValidator::totalDatabases)
|
||||||
|
.asTotalLimitFor(250, HsManagedWebspaceBookingItemValidator::totalEMailAddresses),
|
||||||
integerProperty("Daemons").min(0).max(10).optional(),
|
integerProperty("Daemons").min(0).max(10).optional(),
|
||||||
booleanProperty("Online Office Server").optional()
|
booleanProperty("Online Office Server").optional(),
|
||||||
|
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").optional()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<String> totalUnixUsers() {
|
||||||
|
// FIXME: implement
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> totalDatabaseUsers() {
|
||||||
|
// FIXME: implement
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> totalDatabases() {
|
||||||
|
// FIXME: implement
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> totalEMailAddresses() {
|
||||||
|
// FIXME: implement
|
||||||
|
return emptyList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,37 +1,18 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
|
||||||
|
|
||||||
import java.util.List;
|
class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator {
|
||||||
|
|
||||||
import static java.util.EnumSet.of;
|
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
|
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
|
|
||||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
|
|
||||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
|
||||||
|
|
||||||
class HsPrivateCloudBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
|
|
||||||
|
|
||||||
HsPrivateCloudBookingItemValidator() {
|
HsPrivateCloudBookingItemValidator() {
|
||||||
super(
|
super(
|
||||||
integerProperty("CPUs").min(4).max(128).required(),
|
integerProperty("CPUs").min(4).max(128).required().asTotalLimit(),
|
||||||
integerProperty("RAM").unit("GB").min(4).max(512).required(),
|
integerProperty("RAM").unit("GB").min(4).max(512).required().asTotalLimit(),
|
||||||
integerProperty("SSD").unit("GB").min(100).max(4000).step(25).required(),
|
integerProperty("SSD").unit("GB").min(100).max(4000).step(25).required().asTotalLimit(),
|
||||||
integerProperty("HDD").unit("GB").min(0).max(16000).step(25).optional(),
|
integerProperty("HDD").unit("GB").min(0).max(16000).step(25).withDefault(0).asTotalLimit(),
|
||||||
integerProperty("Traffic").unit("GB").min(1000).max(40000).step(250).required(),
|
integerProperty("Traffic").unit("GB").min(1000).max(40000).step(250).required().asTotalLimit(),
|
||||||
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional()
|
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> validate(final HsBookingItemEntity privateCloudBookingItem) {
|
|
||||||
final var selfValidation = super.validate(privateCloudBookingItem);
|
|
||||||
return !selfValidation.isEmpty() ? selfValidation : validateSubEntities(privateCloudBookingItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<String> validateSubEntities(final HsBookingItemEntity privateCloudBookingItem) {
|
|
||||||
return validateSubEntities(privateCloudBookingItem, of(CLOUD_SERVER, MANAGED_SERVER));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,6 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
import net.hostsharing.hsadminng.hs.validation.Validatable;
|
|
||||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||||
@ -56,7 +55,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
|||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validatable<HsHostingAssetEntity, HsHostingAssetType> {
|
public class HsHostingAssetEntity implements Stringifyable, RbacObject {
|
||||||
|
|
||||||
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
|
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
|
||||||
.withProp(HsHostingAssetEntity::getType)
|
.withProp(HsHostingAssetEntity::getType)
|
||||||
@ -114,16 +113,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
|
|||||||
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg);
|
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getPropertiesName() {
|
|
||||||
return "config";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getProperties() {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return stringify.apply(this);
|
return stringify.apply(this);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators;
|
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||||
|
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
|
||||||
import org.apache.commons.collections4.ListUtils;
|
import org.apache.commons.collections4.ListUtils;
|
||||||
|
|
||||||
import jakarta.validation.ValidationException;
|
import jakarta.validation.ValidationException;
|
||||||
@ -19,23 +20,23 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOU
|
|||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
||||||
|
|
||||||
public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
|
public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity> {
|
||||||
|
|
||||||
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType>> validators = new HashMap<>();
|
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity>> validators = new HashMap<>();
|
||||||
static {
|
static {
|
||||||
register(CLOUD_SERVER, new HsEntityValidator<>());
|
register(CLOUD_SERVER, new HsHostingAssetEntityValidator());
|
||||||
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
|
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
|
||||||
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
|
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> validator) {
|
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity> validator) {
|
||||||
stream(validator.propertyValidators).forEach( entry -> {
|
stream(validator.propertyValidators).forEach( entry -> {
|
||||||
entry.verifyConsistency(Map.entry(type, validator));
|
entry.verifyConsistency(Map.entry(type, validator));
|
||||||
});
|
});
|
||||||
validators.put(type, validator);
|
validators.put(type, validator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> forType(final Enum<HsHostingAssetType> type) {
|
public static HsEntityValidator<HsHostingAssetEntity> forType(final Enum<HsHostingAssetType> type) {
|
||||||
return validators.get(type);
|
return validators.get(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,9 +52,15 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
|
|||||||
return entityToSave;
|
return entityToSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public HsHostingAssetEntityValidator(final ValidatableProperty<?>... properties) {
|
||||||
|
super(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> validate(final HsHostingAssetEntity assetEntity) {
|
public List<String> validate(final HsHostingAssetEntity assetEntity) {
|
||||||
final var selfValidation = super.validate(assetEntity);
|
final var selfValidation = enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig()));
|
||||||
return selfValidation.isEmpty()
|
return selfValidation.isEmpty()
|
||||||
// higher levels are only validated with valid sub-entity
|
// higher levels are only validated with valid sub-entity
|
||||||
? ListUtils.union(
|
? ListUtils.union(
|
||||||
@ -67,6 +74,6 @@ public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAs
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
|
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
|
||||||
return bookingItem != null ? HsBookingItemEntityValidators.validate(bookingItem) : emptyList();
|
return bookingItem != null ? HsBookingItemEntityValidator.doValidate(bookingItem) : emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
|
||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
|
||||||
|
|
||||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator {
|
||||||
|
|
||||||
class HsManagedServerHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
|
|
||||||
|
|
||||||
public HsManagedServerHostingAssetValidator() {
|
public HsManagedServerHostingAssetValidator() {
|
||||||
super(
|
super(
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
|
||||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
class HsManagedWebspaceHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
|
class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator {
|
||||||
public HsManagedWebspaceHostingAssetValidator() {
|
public HsManagedWebspaceHostingAssetValidator() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,29 +8,29 @@ import java.util.Map;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
public class BooleanPropertyValidator extends HsPropertyValidator<Boolean> {
|
public class BooleanProperty extends ValidatableProperty<Boolean> {
|
||||||
|
|
||||||
private Map.Entry<String, String> falseIf;
|
private Map.Entry<String, String> falseIf;
|
||||||
|
|
||||||
private BooleanPropertyValidator(final String propertyName) {
|
private BooleanProperty(final String propertyName) {
|
||||||
super(Boolean.class, propertyName);
|
super(Boolean.class, propertyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BooleanPropertyValidator booleanProperty(final String propertyName) {
|
public static BooleanProperty booleanProperty(final String propertyName) {
|
||||||
return new BooleanPropertyValidator(propertyName);
|
return new BooleanProperty(propertyName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public HsPropertyValidator<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) {
|
public ValidatableProperty<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) {
|
||||||
this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue);
|
this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void validate(final ArrayList<String> result, final String propertiesName, final Boolean propValue, final Map<String, Object> props) {
|
protected void validate(final ArrayList<String> result, final Boolean propValue, final Map<String, Object> props) {
|
||||||
if (falseIf != null && !Objects.equals(props.get(falseIf.getKey()), falseIf.getValue())) {
|
if (falseIf != null && !Objects.equals(props.get(falseIf.getKey()), falseIf.getValue())) {
|
||||||
if (propValue) {
|
if (propValue) {
|
||||||
result.add("'"+propertiesName+"." + propertyName + "' is expected to be false because " +
|
result.add(propertyName + " is expected to be false because " +
|
||||||
propertiesName+"." + falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue);
|
falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.validation;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
public class EnumerationProperty extends ValidatableProperty<String> {
|
||||||
|
|
||||||
|
private String[] values;
|
||||||
|
|
||||||
|
private EnumerationProperty(final String propertyName) {
|
||||||
|
super(String.class, propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EnumerationProperty enumerationProperty(final String propertyName) {
|
||||||
|
return new EnumerationProperty(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidatableProperty<String> values(final String... values) {
|
||||||
|
this.values = values;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void validate(final ArrayList<String> result, final String propValue, final Map<String, Object> props) {
|
||||||
|
if (Arrays.stream(values).noneMatch(v -> v.equals(propValue))) {
|
||||||
|
result.add(propertyName + " is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String simpleTypeName() {
|
||||||
|
return "enumeration";
|
||||||
|
}
|
||||||
|
}
|
@ -1,38 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.validation;
|
|
||||||
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
public class EnumerationPropertyValidator extends HsPropertyValidator<String> {
|
|
||||||
|
|
||||||
private String[] values;
|
|
||||||
|
|
||||||
private EnumerationPropertyValidator(final String propertyName) {
|
|
||||||
super(String.class, propertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static EnumerationPropertyValidator enumerationProperty(final String propertyName) {
|
|
||||||
return new EnumerationPropertyValidator(propertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HsPropertyValidator<String> values(final String... values) {
|
|
||||||
this.values = values;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void validate(final ArrayList<String> result, final String propertiesName, final String propValue, final Map<String, Object> props) {
|
|
||||||
if (Arrays.stream(values).noneMatch(v -> v.equals(propValue))) {
|
|
||||||
result.add("'"+propertiesName+"." + propertyName + "' is expected to be one of " + Arrays.toString(values) + " but is '" + propValue + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String simpleTypeName() {
|
|
||||||
return "enumeration";
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,41 +4,36 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
|||||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static java.util.Arrays.stream;
|
import static java.util.Arrays.stream;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Optional.ofNullable;
|
|
||||||
|
|
||||||
public class HsEntityValidator<E extends Validatable<E, T>, T extends Enum<T>> {
|
public abstract class HsEntityValidator<E> {
|
||||||
|
|
||||||
public final HsPropertyValidator<?>[] propertyValidators;
|
public final ValidatableProperty<?>[] propertyValidators;
|
||||||
|
|
||||||
public HsEntityValidator(final HsPropertyValidator<?>... validators) {
|
public HsEntityValidator(final ValidatableProperty<?>... validators) {
|
||||||
propertyValidators = validators;
|
propertyValidators = validators;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> validate(final E assetEntity) {
|
protected static List<String> enrich(final String prefix, final List<String> messages) {
|
||||||
final var result = new ArrayList<String>();
|
return messages.stream()
|
||||||
assetEntity.getProperties().keySet().forEach( givenPropName -> {
|
.map(message -> prefix + "." + message)
|
||||||
if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) {
|
.toList();
|
||||||
result.add("'"+assetEntity.getPropertiesName()+"." + givenPropName + "' is not expected but is set to '" +assetEntity.getProperties().get(givenPropName) + "'");
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
stream(propertyValidators).forEach(pv -> {
|
protected static String prefix(final String... parts) {
|
||||||
result.addAll(pv.validate(assetEntity.getPropertiesName(), assetEntity.getProperties()));
|
return String.join(".", parts);
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract List<String> validate(final E entity);
|
||||||
|
|
||||||
public final List<Map<String, Object>> properties() {
|
public final List<Map<String, Object>> properties() {
|
||||||
final var mapper = new ObjectMapper();
|
final var mapper = new ObjectMapper();
|
||||||
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
|
||||||
@ -48,45 +43,27 @@ public class HsEntityValidator<E extends Validatable<E, T>, T extends Enum<T>> {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected ArrayList<String> validateProperties(final Map<String, Object> properties) {
|
||||||
|
final var result = new ArrayList<String>();
|
||||||
|
properties.keySet().forEach( givenPropName -> {
|
||||||
|
if (stream(propertyValidators).map(pv -> pv.propertyName).noneMatch(propName -> propName.equals(givenPropName))) {
|
||||||
|
result.add(givenPropName + " is not expected but is set to '" + properties.get(givenPropName) + "'");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stream(propertyValidators).forEach(pv -> {
|
||||||
|
result.addAll(pv.validate(properties));
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
private static Map<String, Object> asKeyValueMap(final Map map) {
|
private static Map<String, Object> asKeyValueMap(final Map map) {
|
||||||
return (Map<String, Object>) map;
|
return (Map<String, Object>) map;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<String> validateSubEntities(
|
|
||||||
final HsBookingItemEntity assetEntity,
|
|
||||||
final EnumSet<HsBookingItemType> subItemTypes) {
|
|
||||||
return properties().stream()
|
|
||||||
.map(prop -> validateMaxTotalValue(assetEntity, subItemTypes, prop))
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String validateMaxTotalValue(
|
|
||||||
final HsBookingItemEntity assetEntity,
|
|
||||||
final EnumSet<HsBookingItemType> subTypes,
|
|
||||||
final Map<String, Object> propDef) {
|
|
||||||
final var propName = propDef.get("propertyName").toString();
|
|
||||||
final var propUnit = ofNullable(propDef.get("unit")).map(u -> " " + u).orElse("");
|
|
||||||
final var totalValue = ofNullable(assetEntity.getSubBookingItems()).orElse(emptyList())
|
|
||||||
.stream()
|
|
||||||
.filter(subItem -> subTypes.contains(subItem.getType()))
|
|
||||||
.map(subItem -> toInteger(subItem.getResources().get(propName)))
|
|
||||||
.reduce(0, Integer::sum);
|
|
||||||
final var maxValue = toInteger(assetEntity.getResources().get(propName));
|
|
||||||
return totalValue > maxValue
|
|
||||||
? "total %s is %d%s exceeds max total %s %d%s".formatted(
|
|
||||||
propName, totalValue, propUnit, propName, maxValue, propUnit)
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected List<String> validateParentEntities(final HsBookingItemEntity bookingItem) {
|
protected List<String> validateParentEntities(final HsBookingItemEntity bookingItem) {
|
||||||
return bookingItem.getParentItem() != null
|
return bookingItem.getParentItem() != null
|
||||||
? HsBookingItemEntityValidators.validate(bookingItem.getParentItem())
|
? HsBookingItemEntityValidator.doValidate(bookingItem.getParentItem())
|
||||||
: emptyList();
|
: emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer toInteger(final Object value) {
|
|
||||||
return value == null ? 0 : (Integer) value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,62 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.validation;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
public abstract class HsPropertyValidator<T> {
|
|
||||||
|
|
||||||
final Class<T> type;
|
|
||||||
final String propertyName;
|
|
||||||
private Boolean required;
|
|
||||||
|
|
||||||
public HsPropertyValidator<T> required() {
|
|
||||||
required = Boolean.TRUE;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HsPropertyValidator<T> optional() {
|
|
||||||
required = Boolean.FALSE;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final List<String> validate(final String propertiesName, final Map<String, Object> props) {
|
|
||||||
final var result = new ArrayList<String>();
|
|
||||||
final var propValue = props.get(propertyName);
|
|
||||||
if (propValue == null) {
|
|
||||||
if (required) {
|
|
||||||
result.add("'"+propertiesName+"." + propertyName + "' is required but missing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (propValue != null){
|
|
||||||
if ( type.isInstance(propValue)) {
|
|
||||||
//noinspection unchecked
|
|
||||||
validate(result, propertiesName, (T) propValue, props);
|
|
||||||
} else {
|
|
||||||
result.add("'"+propertiesName+"." + propertyName + "' is expected to be of type " + type + ", " +
|
|
||||||
"but is of type '" + propValue.getClass().getSimpleName() + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void validate(final ArrayList<String> result, final String propertiesName, final T propValue, final Map<String, Object> props);
|
|
||||||
|
|
||||||
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
|
|
||||||
if (required == null ) {
|
|
||||||
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Object> toMap(final ObjectMapper mapper) {
|
|
||||||
final Map<String, Object> map = mapper.convertValue(this, Map.class);
|
|
||||||
map.put("type", simpleTypeName());
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract String simpleTypeName();
|
|
||||||
}
|
|
@ -0,0 +1,46 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.validation;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
public class IntegerProperty extends ValidatableProperty<Integer> {
|
||||||
|
|
||||||
|
private String unit;
|
||||||
|
private Integer min;
|
||||||
|
private Integer max;
|
||||||
|
private Integer step;
|
||||||
|
|
||||||
|
public static IntegerProperty integerProperty(final String propertyName) {
|
||||||
|
return new IntegerProperty(propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IntegerProperty(final String propertyName) {
|
||||||
|
super(Integer.class, propertyName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String unit() {
|
||||||
|
return unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void validate(final ArrayList<String> result, final Integer propValue, final Map<String, Object> props) {
|
||||||
|
if (min != null && propValue < min) {
|
||||||
|
result.add(propertyName + " is expected to be >= " + min + " but is " + propValue);
|
||||||
|
}
|
||||||
|
if (max != null && propValue > max) {
|
||||||
|
result.add(propertyName + " is expected to be <= " + max + " but is " + propValue);
|
||||||
|
}
|
||||||
|
if (step != null && propValue % step != 0) {
|
||||||
|
result.add(propertyName + " is expected to be multiple of " + step + " but is " + propValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String simpleTypeName() {
|
||||||
|
return "integer";
|
||||||
|
}
|
||||||
|
}
|
@ -1,42 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.validation;
|
|
||||||
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Setter
|
|
||||||
public class IntegerPropertyValidator extends HsPropertyValidator<Integer> {
|
|
||||||
|
|
||||||
private String unit;
|
|
||||||
private Integer min;
|
|
||||||
private Integer max;
|
|
||||||
private Integer step;
|
|
||||||
|
|
||||||
public static IntegerPropertyValidator integerProperty(final String propertyName) {
|
|
||||||
return new IntegerPropertyValidator(propertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntegerPropertyValidator(final String propertyName) {
|
|
||||||
super(Integer.class, propertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void validate(final ArrayList<String> result, final String propertiesName, final Integer propValue, final Map<String, Object> props) {
|
|
||||||
if (min != null && propValue < min) {
|
|
||||||
result.add("'"+propertiesName+"." + propertyName + "' is expected to be >= " + min + " but is " + propValue);
|
|
||||||
}
|
|
||||||
if (max != null && propValue > max) {
|
|
||||||
result.add("'"+propertiesName+"." + propertyName + "' is expected to be <= " + max + " but is " + propValue);
|
|
||||||
}
|
|
||||||
if (step != null && propValue % step != 0) {
|
|
||||||
result.add("'"+propertiesName+"." + propertyName + "' is expected to be multiple of " + step + " but is " + propValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String simpleTypeName() {
|
|
||||||
return "integer";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package net.hostsharing.hsadminng.hs.validation;
|
|
||||||
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public interface Validatable<E, T extends Enum<T>> {
|
|
||||||
|
|
||||||
|
|
||||||
Enum<T> getType();
|
|
||||||
|
|
||||||
String getPropertiesName();
|
|
||||||
Map<String, Object> getProperties();
|
|
||||||
}
|
|
@ -0,0 +1,109 @@
|
|||||||
|
package net.hostsharing.hsadminng.hs.validation;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static java.lang.Boolean.FALSE;
|
||||||
|
import static java.lang.Boolean.TRUE;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public abstract class ValidatableProperty<T> {
|
||||||
|
|
||||||
|
final Class<T> type;
|
||||||
|
final String propertyName;
|
||||||
|
private Boolean required;
|
||||||
|
private T defaultValue;
|
||||||
|
private Supplier<List<String>> asTotalLimitValidator;
|
||||||
|
|
||||||
|
public String unit() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidatableProperty<T> required() {
|
||||||
|
required = TRUE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidatableProperty<T> optional() {
|
||||||
|
required = FALSE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidatableProperty<T> withDefault(final T value) {
|
||||||
|
defaultValue = value;
|
||||||
|
required = FALSE;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidatableProperty<T> asTotalLimit() {
|
||||||
|
asTotalLimitValidator = ValidatableProperty::directTotalLimitValidator;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String propertyName() {
|
||||||
|
return propertyName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isTotalsValidator() {
|
||||||
|
return asTotalLimitValidator != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidatableProperty<T> asTotalLimitFor(final T factor, final Supplier<List<String>> validator) {
|
||||||
|
// FIXME: implement i
|
||||||
|
asTotalLimitValidator = validator; // FIXME: implement multiple
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final List<String> validate(final Map<String, Object> props) {
|
||||||
|
final var result = new ArrayList<String>();
|
||||||
|
final var propValue = props.get(propertyName);
|
||||||
|
if (propValue == null) {
|
||||||
|
if (required) {
|
||||||
|
result.add(propertyName + " is required but missing");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (propValue != null){
|
||||||
|
if ( type.isInstance(propValue)) {
|
||||||
|
//noinspection unchecked
|
||||||
|
validate(result, (T) propValue, props);
|
||||||
|
} else {
|
||||||
|
result.add(propertyName + " is expected to be of type " + type + ", " +
|
||||||
|
"but is of type '" + propValue.getClass().getSimpleName() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void validate(final ArrayList<String> result, final T propValue, final Map<String, Object> props);
|
||||||
|
|
||||||
|
public void verifyConsistency(final Map.Entry<? extends Enum<?>, ?> typeDef) {
|
||||||
|
if (required == null ) {
|
||||||
|
throw new IllegalStateException(typeDef.getKey() + "[" + propertyName + "] not fully initialized, please call either .required() or .optional()" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static List<String> directTotalLimitValidator() {
|
||||||
|
return emptyList(); // FIXME: implement
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> toMap(final ObjectMapper mapper) {
|
||||||
|
final Map<String, Object> map = mapper.convertValue(this, Map.class);
|
||||||
|
map.put("type", simpleTypeName());
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public T getValue(final Map<String, Object> propValues) {
|
||||||
|
return (T) Optional.ofNullable(propValues.get(propertyName)).orElse(defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract String simpleTypeName();
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jakarta.validation.ValidationException;
|
import jakarta.validation.ValidationException;
|
||||||
@ -9,17 +11,26 @@ import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVAT
|
|||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_WEBSPACE;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.valid;
|
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.valid;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.catchThrowable;
|
import static org.assertj.core.api.Assertions.catchThrowable;
|
||||||
|
|
||||||
class HsBookingItemEntityValidatorsUnitTest {
|
class HsBookingItemEntityValidatorUnitTest {
|
||||||
|
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||||
|
.debitorNumber(12345)
|
||||||
|
.build();
|
||||||
|
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
|
||||||
|
.debitor(debitor)
|
||||||
|
.caption("test project")
|
||||||
|
.build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validThrowsException() {
|
void validThrowsException() {
|
||||||
// given
|
// given
|
||||||
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
|
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
|
||||||
.type(CLOUD_SERVER)
|
.type(CLOUD_SERVER)
|
||||||
|
.project(project)
|
||||||
|
.caption("Test-Server")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
@ -28,16 +39,16 @@ class HsBookingItemEntityValidatorsUnitTest {
|
|||||||
// then
|
// then
|
||||||
assertThat(result).isInstanceOf(ValidationException.class)
|
assertThat(result).isInstanceOf(ValidationException.class)
|
||||||
.hasMessageContaining(
|
.hasMessageContaining(
|
||||||
"'resources.CPUs' is required but missing",
|
"D-12345:test project:Test-Server.resources.CPUs is required but missing",
|
||||||
"'resources.RAM' is required but missing",
|
"D-12345:test project:Test-Server.resources.RAM is required but missing",
|
||||||
"'resources.SSD' is required but missing",
|
"D-12345:test project:Test-Server.resources.SSD is required but missing",
|
||||||
"'resources.Traffic' is required but missing");
|
"D-12345:test project:Test-Server.resources.Traffic is required but missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void listsTypes() {
|
void listsTypes() {
|
||||||
// when
|
// when
|
||||||
final var result = HsBookingItemEntityValidators.types();
|
final var result = HsBookingItemEntityValidator.types();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE);
|
assertThat(result).containsExactlyInAnyOrder(PRIVATE_CLOUD, CLOUD_SERVER, MANAGED_SERVER, MANAGED_WEBSPACE);
|
@ -1,6 +1,8 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -11,16 +13,26 @@ import static java.util.Map.ofEntries;
|
|||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType;
|
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.forType;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
class HsCloudServerBookingItemValidatorUnitTest {
|
class HsCloudServerBookingItemValidatorUnitTest {
|
||||||
|
|
||||||
|
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||||
|
.debitorNumber(12345)
|
||||||
|
.build();
|
||||||
|
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
|
||||||
|
.debitor(debitor)
|
||||||
|
.caption("Test-Project")
|
||||||
|
.build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validatesProperties() {
|
void validatesProperties() {
|
||||||
// given
|
// given
|
||||||
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
|
final var cloudServerBookingItemEntity = HsBookingItemEntity.builder()
|
||||||
.type(CLOUD_SERVER)
|
.type(CLOUD_SERVER)
|
||||||
|
.project(project)
|
||||||
|
.caption("Test-Server")
|
||||||
.resources(Map.ofEntries(
|
.resources(Map.ofEntries(
|
||||||
entry("CPUs", 2),
|
entry("CPUs", 2),
|
||||||
entry("RAM", 25),
|
entry("RAM", 25),
|
||||||
@ -31,10 +43,10 @@ class HsCloudServerBookingItemValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = HsBookingItemEntityValidators.validate(cloudServerBookingItemEntity);
|
final var result = HsBookingItemEntityValidator.doValidate(cloudServerBookingItemEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactly("'resources.SLA-EMail' is not expected but is set to 'true'");
|
assertThat(result).containsExactly("D-12345:Test-Project:Test-Server.resources.SLA-EMail is not expected but is set to 'true'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -44,12 +56,12 @@ class HsCloudServerBookingItemValidatorUnitTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||||
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}",
|
"{type=integer, propertyName=CPUs, required=true, defaultValue=null, asTotalLimitValidator=null, unit=null, min=1, max=32, step=null, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}",
|
"{type=integer, propertyName=RAM, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=1, max=128, step=null, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}",
|
"{type=integer, propertyName=SSD, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=25, max=1000, step=25, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}",
|
"{type=integer, propertyName=HDD, required=false, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=4000, step=250, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}",
|
"{type=integer, propertyName=Traffic, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=250, max=10000, step=250, totalsValidator=false}",
|
||||||
"{type=enumeration, propertyName=SLA-Infrastructure, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}");
|
"{type=enumeration, propertyName=SLA-Infrastructure, required=false, defaultValue=null, asTotalLimitValidator=null, values=[BASIC, EXT8H, EXT4H, EXT2H], totalsValidator=false}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -57,6 +69,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
|
|||||||
// given
|
// given
|
||||||
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
|
final var subCloudServerBookingItemEntity = HsBookingItemEntity.builder()
|
||||||
.type(CLOUD_SERVER)
|
.type(CLOUD_SERVER)
|
||||||
|
.caption("Test Cloud-Server")
|
||||||
.resources(ofEntries(
|
.resources(ofEntries(
|
||||||
entry("CPUs", 2),
|
entry("CPUs", 2),
|
||||||
entry("RAM", 10),
|
entry("RAM", 10),
|
||||||
@ -66,6 +79,7 @@ class HsCloudServerBookingItemValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
|
final HsBookingItemEntity subManagedServerBookingItemEntity = HsBookingItemEntity.builder()
|
||||||
.type(MANAGED_SERVER)
|
.type(MANAGED_SERVER)
|
||||||
|
.caption("Test Managed-Server")
|
||||||
.resources(ofEntries(
|
.resources(ofEntries(
|
||||||
entry("CPUs", 3),
|
entry("CPUs", 3),
|
||||||
entry("RAM", 20),
|
entry("RAM", 20),
|
||||||
@ -75,6 +89,8 @@ class HsCloudServerBookingItemValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
|
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
|
||||||
.type(PRIVATE_CLOUD)
|
.type(PRIVATE_CLOUD)
|
||||||
|
.project(project)
|
||||||
|
.caption("Test Cloud")
|
||||||
.resources(ofEntries(
|
.resources(ofEntries(
|
||||||
entry("CPUs", 4),
|
entry("CPUs", 4),
|
||||||
entry("RAM", 20),
|
entry("RAM", 20),
|
||||||
@ -90,14 +106,14 @@ class HsCloudServerBookingItemValidatorUnitTest {
|
|||||||
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
|
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = HsBookingItemEntityValidators.validate(subCloudServerBookingItemEntity);
|
final var result = HsBookingItemEntityValidator.doValidate(subCloudServerBookingItemEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
"total CPUs is 5 exceeds max total CPUs 4",
|
"D-12345:Test-Project:Test Cloud-Server.parentItem.total CPUs is 5 exceeds max total CPUs 4",
|
||||||
"total RAM is 30 GB exceeds max total RAM 20 GB",
|
"D-12345:Test-Project:Test Cloud-Server.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
|
||||||
"total SSD is 150 GB exceeds max total SSD 100 GB",
|
"D-12345:Test-Project:Test Cloud-Server.parentItem.total SSD is 150 GB exceeds max total SSD 100 GB",
|
||||||
"total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
|
"D-12345:Test-Project:Test Cloud-Server.parentItem.total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -11,16 +13,25 @@ import static java.util.Map.ofEntries;
|
|||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.CLOUD_SERVER;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.MANAGED_SERVER;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
|
import static net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType.PRIVATE_CLOUD;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.forType;
|
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidator.forType;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
class HsManagedServerBookingItemValidatorUnitTest {
|
class HsManagedServerBookingItemValidatorUnitTest {
|
||||||
|
|
||||||
|
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||||
|
.debitorNumber(12345)
|
||||||
|
.build();
|
||||||
|
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
|
||||||
|
.debitor(debitor)
|
||||||
|
.caption("Test-Project")
|
||||||
|
.build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validatesProperties() {
|
void validatesProperties() {
|
||||||
// given
|
// given
|
||||||
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
|
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
|
||||||
.type(MANAGED_SERVER)
|
.type(MANAGED_SERVER)
|
||||||
|
.project(project)
|
||||||
.resources(Map.ofEntries(
|
.resources(Map.ofEntries(
|
||||||
entry("CPUs", 2),
|
entry("CPUs", 2),
|
||||||
entry("RAM", 25),
|
entry("RAM", 25),
|
||||||
@ -31,10 +42,10 @@ class HsManagedServerBookingItemValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = HsBookingItemEntityValidators.validate(mangedServerBookingItemEntity);
|
final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactly("'resources.SLA-EMail' is expected to be false because resources.SLA-Platform=BASIC but is true");
|
assertThat(result).containsExactly("D-12345:Test-Project:null.resources.SLA-EMail is expected to be false because SLA-Platform=BASIC but is true");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -44,17 +55,17 @@ class HsManagedServerBookingItemValidatorUnitTest {
|
|||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||||
"{type=integer, propertyName=CPUs, required=true, unit=null, min=1, max=32, step=null}",
|
"{type=integer, propertyName=CPUs, required=true, defaultValue=null, asTotalLimitValidator=null, unit=null, min=1, max=32, step=null, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=RAM, required=true, unit=GB, min=1, max=128, step=null}",
|
"{type=integer, propertyName=RAM, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=1, max=128, step=null, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=SSD, required=true, unit=GB, min=25, max=1000, step=25}",
|
"{type=integer, propertyName=SSD, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=25, max=1000, step=25, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=4000, step=250}",
|
"{type=integer, propertyName=HDD, required=false, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=4000, step=250, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=250, max=10000, step=250}",
|
"{type=integer, propertyName=Traffic, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=250, max=10000, step=250, totalsValidator=false}",
|
||||||
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT8H, EXT4H, EXT2H]}",
|
"{type=enumeration, propertyName=SLA-Platform, required=false, defaultValue=null, asTotalLimitValidator=null, values=[BASIC, EXT8H, EXT4H, EXT2H], totalsValidator=false}",
|
||||||
"{type=boolean, propertyName=SLA-EMail, required=false, falseIf={SLA-Platform=BASIC}}",
|
"{type=boolean, propertyName=SLA-EMail, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
|
||||||
"{type=boolean, propertyName=SLA-Maria, required=false, falseIf={SLA-Platform=BASIC}}",
|
"{type=boolean, propertyName=SLA-Maria, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
|
||||||
"{type=boolean, propertyName=SLA-PgSQL, required=false, falseIf={SLA-Platform=BASIC}}",
|
"{type=boolean, propertyName=SLA-PgSQL, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
|
||||||
"{type=boolean, propertyName=SLA-Office, required=false, falseIf={SLA-Platform=BASIC}}",
|
"{type=boolean, propertyName=SLA-Office, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}",
|
||||||
"{type=boolean, propertyName=SLA-Web, required=false, falseIf={SLA-Platform=BASIC}}");
|
"{type=boolean, propertyName=SLA-Web, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf={SLA-Platform=BASIC}, totalsValidator=false}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -80,6 +91,7 @@ class HsManagedServerBookingItemValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
|
final var privateCloudBookingItemEntity = HsBookingItemEntity.builder()
|
||||||
.type(PRIVATE_CLOUD)
|
.type(PRIVATE_CLOUD)
|
||||||
|
.project(project)
|
||||||
.resources(ofEntries(
|
.resources(ofEntries(
|
||||||
entry("CPUs", 4),
|
entry("CPUs", 4),
|
||||||
entry("RAM", 20),
|
entry("RAM", 20),
|
||||||
@ -91,18 +103,19 @@ class HsManagedServerBookingItemValidatorUnitTest {
|
|||||||
subCloudServerBookingItemEntity
|
subCloudServerBookingItemEntity
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
subManagedServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
|
subManagedServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
|
||||||
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
|
subCloudServerBookingItemEntity.setParentItem(privateCloudBookingItemEntity);
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = HsBookingItemEntityValidators.validate(subManagedServerBookingItemEntity);
|
final var result = HsBookingItemEntityValidator.doValidate(subManagedServerBookingItemEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
"total CPUs is 5 exceeds max total CPUs 4",
|
"D-12345:Test-Project:null.parentItem.total CPUs is 5 exceeds max total CPUs 4",
|
||||||
"total RAM is 30 GB exceeds max total RAM 20 GB",
|
"D-12345:Test-Project:null.parentItem.total RAM is 30 GB exceeds max total RAM 20 GB",
|
||||||
"total SSD is 150 GB exceeds max total SSD 100 GB",
|
"D-12345:Test-Project:null.parentItem.total SSD is 150 GB exceeds max total SSD 100 GB",
|
||||||
"total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
|
"D-12345:Test-Project:null.parentItem.total Traffic is 5500 GB exceeds max total Traffic 5000 GB"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||||
|
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.debitor.HsBookingDebitorEntity;
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -11,11 +13,21 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|||||||
|
|
||||||
class HsManagedWebspaceBookingItemValidatorUnitTest {
|
class HsManagedWebspaceBookingItemValidatorUnitTest {
|
||||||
|
|
||||||
|
final HsBookingDebitorEntity debitor = HsBookingDebitorEntity.builder()
|
||||||
|
.debitorNumber(12345)
|
||||||
|
.build();
|
||||||
|
final HsBookingProjectEntity project = HsBookingProjectEntity.builder()
|
||||||
|
.debitor(debitor)
|
||||||
|
.caption("Test-Project")
|
||||||
|
.build();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void validatesProperties() {
|
void validatesProperties() {
|
||||||
// given
|
// given
|
||||||
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
|
final var mangedServerBookingItemEntity = HsBookingItemEntity.builder()
|
||||||
.type(MANAGED_WEBSPACE)
|
.type(MANAGED_WEBSPACE)
|
||||||
|
.project(project)
|
||||||
|
.caption("Test Managed-Webspace")
|
||||||
.resources(Map.ofEntries(
|
.resources(Map.ofEntries(
|
||||||
entry("CPUs", 2),
|
entry("CPUs", 2),
|
||||||
entry("RAM", 25),
|
entry("RAM", 25),
|
||||||
@ -26,27 +38,29 @@ class HsManagedWebspaceBookingItemValidatorUnitTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = HsBookingItemEntityValidators.validate(mangedServerBookingItemEntity);
|
final var result = HsBookingItemEntityValidator.doValidate(mangedServerBookingItemEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
"'resources.CPUs' is not expected but is set to '2'",
|
"D-12345:Test-Project:Test Managed-Webspace.resources.SLA-EMail is not expected but is set to 'true'",
|
||||||
"'resources.SLA-EMail' is not expected but is set to 'true'",
|
"D-12345:Test-Project:Test Managed-Webspace.resources.CPUs is not expected but is set to '2'",
|
||||||
"'resources.RAM' is not expected but is set to '25'");
|
"D-12345:Test-Project:Test Managed-Webspace.resources.RAM is not expected but is set to '25'",
|
||||||
|
"D-12345:Test-Project:Test Managed-Webspace.resources.MultiOptions is required but missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void containsAllValidations() {
|
void containsAllValidations() {
|
||||||
// when
|
// when
|
||||||
final var validator = HsBookingItemEntityValidators.forType(MANAGED_WEBSPACE);
|
final var validator = HsBookingItemEntityValidator.forType(MANAGED_WEBSPACE);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
assertThat(validator.properties()).map(Map::toString).containsExactlyInAnyOrder(
|
||||||
"{type=integer, propertyName=SSD, required=true, unit=GB, min=1, max=100, step=1}",
|
"{type=integer, propertyName=SSD, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=1, max=100, step=1, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=HDD, required=false, unit=GB, min=0, max=250, step=10}",
|
"{type=integer, propertyName=HDD, required=false, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=0, max=250, step=10, totalsValidator=false}",
|
||||||
"{type=integer, propertyName=Traffic, required=true, unit=GB, min=10, max=1000, step=10}",
|
"{type=integer, propertyName=Traffic, required=true, defaultValue=null, asTotalLimitValidator=null, unit=GB, min=10, max=1000, step=10, totalsValidator=false}",
|
||||||
"{type=enumeration, propertyName=SLA-Platform, required=false, values=[BASIC, EXT24H]}",
|
"{type=integer, propertyName=MultiOptions, required=true, defaultValue=null, asTotalLimitValidator={}, unit=null, min=1, max=100, step=1, totalsValidator=true}",
|
||||||
"{type=integer, propertyName=Daemons, required=false, unit=null, min=0, max=10, step=null}",
|
"{type=integer, propertyName=Daemons, required=false, defaultValue=null, asTotalLimitValidator=null, unit=null, min=0, max=10, step=null, totalsValidator=false}",
|
||||||
"{type=boolean, propertyName=Online Office Server, required=false, falseIf=null}");
|
"{type=boolean, propertyName=Online Office Server, required=false, defaultValue=null, asTotalLimitValidator=null, falseIf=null, totalsValidator=false}",
|
||||||
|
"{type=enumeration, propertyName=SLA-Platform, required=false, defaultValue=null, asTotalLimitValidator=null, values=[BASIC, EXT24H], totalsValidator=false}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ class HsPrivateCloudBookingItemValidatorTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = HsBookingItemEntityValidators.validate(privateCloudBookingItemEntity);
|
final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).isEmpty();
|
assertThat(result).isEmpty();
|
||||||
@ -88,7 +88,7 @@ class HsPrivateCloudBookingItemValidatorTest {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
final var result = HsBookingItemEntityValidators.validate(privateCloudBookingItemEntity);
|
final var result = HsBookingItemEntityValidator.doValidate(privateCloudBookingItemEntity);
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assertThat(result).containsExactlyInAnyOrder(
|
assertThat(result).containsExactlyInAnyOrder(
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||||
|
|
||||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||||
|
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static java.util.Map.entry;
|
import static java.util.Map.entry;
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
|
|
||||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
|
import static net.hostsharing.hsadminng.hs.booking.project.TestHsBookingProject.TEST_PROJECT;
|
||||||
@ -16,14 +17,15 @@ class HsManagedWebspaceHostingAssetValidatorUnitTest {
|
|||||||
|
|
||||||
final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder()
|
final HsBookingItemEntity managedServerBookingItem = HsBookingItemEntity.builder()
|
||||||
.project(TEST_PROJECT)
|
.project(TEST_PROJECT)
|
||||||
|
.type(HsBookingItemType.MANAGED_SERVER)
|
||||||
.build();
|
.build();
|
||||||
final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder()
|
final HsHostingAssetEntity mangedServerAssetEntity = HsHostingAssetEntity.builder()
|
||||||
.type(MANAGED_SERVER)
|
.type(HsHostingAssetType.MANAGED_SERVER)
|
||||||
.bookingItem(managedServerBookingItem)
|
.bookingItem(managedServerBookingItem)
|
||||||
.config(Map.ofEntries(
|
.config(Map.ofEntries(
|
||||||
entry("HDD", 0),
|
entry("monit_max_ssd_usage", 70),
|
||||||
entry("SSD", 1),
|
entry("monit_max_cpu_usage", 80),
|
||||||
entry("Traffic", 10)
|
entry("monit_max_ram_usage", 90)
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user