hierarchical-validation-baseline (#59)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #59 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
parent
fc2b437a55
commit
46dc653174
@ -9,7 +9,7 @@ import org.springframework.web.context.request.WebRequest;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
class CustomErrorResponse {
|
||||
public class CustomErrorResponse {
|
||||
|
||||
static ResponseEntity<CustomErrorResponse> errorResponse(
|
||||
final WebRequest request,
|
||||
|
@ -0,0 +1,19 @@
|
||||
package net.hostsharing.hsadminng.errors;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.List;
|
||||
|
||||
import static java.lang.String.join;
|
||||
|
||||
public class MultiValidationException extends ValidationException {
|
||||
|
||||
private MultiValidationException(final List<String> violations) {
|
||||
super("[\n" + join(",\n", violations) + "\n]");
|
||||
}
|
||||
|
||||
public static void throwInvalid(final List<String> violations) {
|
||||
if (!violations.isEmpty()) {
|
||||
throw new MultiValidationException(violations);
|
||||
}
|
||||
}
|
||||
}
|
@ -73,9 +73,10 @@ public class RestResponseEntityExceptionHandler
|
||||
}
|
||||
|
||||
@ExceptionHandler({ Iban4jException.class, ValidationException.class })
|
||||
protected ResponseEntity<CustomErrorResponse> handleIbanAndBicExceptions(
|
||||
protected ResponseEntity<CustomErrorResponse> handleValidationExceptions(
|
||||
final Throwable exc, final WebRequest request) {
|
||||
final var message = line(NestedExceptionUtils.getMostSpecificCause(exc).getMessage(), 0);
|
||||
final String fullMessage = NestedExceptionUtils.getMostSpecificCause(exc).getMessage();
|
||||
final var message = exc instanceof MultiValidationException ? fullMessage : line(fullMessage, 0);
|
||||
return errorResponse(request, HttpStatus.BAD_REQUEST, message);
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.hs.booking.generated.api.v1.api.HsBookingItemsA
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemPatchResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.generated.api.v1.model.HsBookingItemResource;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
|
||||
import net.hostsharing.hsadminng.mapper.KeyValueMap;
|
||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@ -13,11 +14,12 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
|
||||
import jakarta.persistence.EntityManager;
|
||||
import jakarta.persistence.PersistenceContext;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidators.valid;
|
||||
import static net.hostsharing.hsadminng.mapper.PostgresDateRange.toPostgresDateRange;
|
||||
|
||||
@RestController
|
||||
@ -32,6 +34,9 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
@Autowired
|
||||
private HsBookingItemRepository bookingItemRepo;
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public ResponseEntity<List<HsBookingItemResource>> listBookingItemsByProjectUuid(
|
||||
@ -57,7 +62,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
|
||||
final var entityToSave = mapper.map(body, HsBookingItemEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var saved = bookingItemRepo.save(valid(entityToSave));
|
||||
final var saved = HsBookingItemEntityValidatorRegistry.validated(bookingItemRepo.save(entityToSave));
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
@ -78,6 +83,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var result = bookingItemRepo.findByUuid(bookingItemUuid);
|
||||
result.ifPresent(entity -> em.detach(entity)); // prevent further LAZY-loading
|
||||
return result
|
||||
.map(bookingItemEntity -> ResponseEntity.ok(
|
||||
mapper.map(bookingItemEntity, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER)))
|
||||
@ -112,7 +118,7 @@ public class HsBookingItemController implements HsBookingItemsApi {
|
||||
|
||||
new HsBookingItemEntityPatcher(current).apply(body);
|
||||
|
||||
final var saved = bookingItemRepo.save(valid(current));
|
||||
final var saved = bookingItemRepo.save(HsBookingItemEntityValidatorRegistry.validated(current));
|
||||
final var mapped = mapper.map(saved, HsBookingItemResource.class, ENTITY_TO_RESOURCE_POSTMAPPER);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.Validatable;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||
@ -19,6 +19,7 @@ import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
@ -27,12 +28,14 @@ import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import jakarta.persistence.Version;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -62,7 +65,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatable<HsBookingItemEntity, HsBookingItemType> {
|
||||
public class HsBookingItemEntity implements Stringifyable, RbacObject {
|
||||
|
||||
private static Stringify<HsBookingItemEntity> stringify = stringify(HsBookingItemEntity.class)
|
||||
.withProp(HsBookingItemEntity::getProject)
|
||||
@ -105,6 +108,14 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
|
||||
@Column(columnDefinition = "resources")
|
||||
private Map<String, Object> resources = new HashMap<>();
|
||||
|
||||
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
@JoinColumn(name="parentitemuuid", referencedColumnName="uuid")
|
||||
private List<HsBookingItemEntity> subBookingItems;
|
||||
|
||||
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
@JoinColumn(name="bookingitemuuid", referencedColumnName="uuid")
|
||||
private List<HsHostingAssetEntity> subHostingAssets;
|
||||
|
||||
@Transient
|
||||
private PatchableMapWrapper<Object> resourcesWrapper;
|
||||
|
||||
@ -150,16 +161,6 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject, Validatab
|
||||
return parentItem == null ? null : parentItem.relatedProject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPropertiesName() {
|
||||
return "resources";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getProperties() {
|
||||
return resources;
|
||||
}
|
||||
|
||||
public HsBookingProjectEntity getRelatedProject() {
|
||||
return project != null ? project : parentItem.getRelatedProject();
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
public class HsBookingItemEntityValidator extends HsEntityValidator<HsBookingItemEntity> {
|
||||
|
||||
public HsBookingItemEntityValidator(final ValidatableProperty<?>... properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public List<String> validate(final HsBookingItemEntity bookingItem) {
|
||||
return sequentiallyValidate(
|
||||
() -> validateProperties(bookingItem),
|
||||
() -> optionallyValidate(bookingItem.getParentItem()),
|
||||
() -> validateAgainstSubEntities(bookingItem)
|
||||
);
|
||||
}
|
||||
|
||||
private List<String> validateProperties(final HsBookingItemEntity bookingItem) {
|
||||
return enrich(prefix(bookingItem.toShortString(), "resources"), validateProperties(bookingItem.getResources()));
|
||||
}
|
||||
|
||||
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
|
||||
return bookingItem != null
|
||||
? enrich(prefix(bookingItem.toShortString(), ""),
|
||||
HsBookingItemEntityValidatorRegistry.doValidate(bookingItem))
|
||||
: emptyList();
|
||||
}
|
||||
|
||||
protected List<String> validateAgainstSubEntities(final HsBookingItemEntity bookingItem) {
|
||||
return enrich(prefix(bookingItem.toShortString(), "resources"),
|
||||
Stream.concat(
|
||||
stream(propertyValidators)
|
||||
.map(propDef -> propDef.validateTotals(bookingItem))
|
||||
.flatMap(Collection::stream),
|
||||
stream(propertyValidators)
|
||||
.filter(ValidatableProperty::isTotalsValidator)
|
||||
.map(prop -> validateMaxTotalValue(bookingItem, prop))
|
||||
).filter(Objects::nonNull).toList());
|
||||
}
|
||||
|
||||
// TODO.refa: convert into generic shape like multi-options validator
|
||||
private static 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::toNonNullInteger)
|
||||
.reduce(0, Integer::sum);
|
||||
final var maxValue = getNonNullIntegerValue(propDef, bookingItem.getResources());
|
||||
if (propDef.thresholdPercentage() != null ) {
|
||||
return totalValue > (maxValue * propDef.thresholdPercentage() / 100)
|
||||
? "%s' maximum total is %d%s, but actual total %s %d%s, which exceeds threshold of %d%%"
|
||||
.formatted(propName, maxValue, propUnit, propName, totalValue, propUnit, propDef.thresholdPercentage())
|
||||
: null;
|
||||
} else {
|
||||
return totalValue > maxValue
|
||||
? "%s' maximum total is %d%s, but actual total %s %d%s"
|
||||
.formatted(propName, maxValue, propUnit, propName, totalValue, propUnit)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
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 net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@ -14,37 +14,42 @@ 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 {
|
||||
public class HsBookingItemEntityValidatorRegistry {
|
||||
|
||||
private static final Map<Enum<HsBookingItemType>, HsEntityValidator<HsBookingItemEntity, HsBookingItemType>> validators = new HashMap<>();
|
||||
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, HsBookingItemType> validator) {
|
||||
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, HsBookingItemType> forType(final Enum<HsBookingItemType> type) {
|
||||
return validators.get(type);
|
||||
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 = HsBookingItemEntityValidators.forType(entityToSave.getType()).validate(entityToSave);
|
||||
if (!violations.isEmpty()) {
|
||||
throw new ValidationException(violations.toString());
|
||||
}
|
||||
public static List<String> doValidate(final HsBookingItemEntity bookingItem) {
|
||||
return HsBookingItemEntityValidatorRegistry.forType(bookingItem.getType()).validate(bookingItem);
|
||||
}
|
||||
|
||||
public static HsBookingItemEntity validated(final HsBookingItemEntity entityToSave) {
|
||||
MultiValidationException.throwInvalid(doValidate(entityToSave));
|
||||
return entityToSave;
|
||||
}
|
||||
}
|
@ -1,20 +1,18 @@
|
||||
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 static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
||||
|
||||
class HsCloudServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
|
||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||
|
||||
class HsCloudServerBookingItemValidator extends HsBookingItemEntityValidator {
|
||||
|
||||
HsCloudServerBookingItemValidator() {
|
||||
super(
|
||||
integerProperty("CPUs").min(1).max(32).required(),
|
||||
integerProperty("RAM").unit("GB").min(1).max(128).required(),
|
||||
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(),
|
||||
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(),
|
||||
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).withDefault(0),
|
||||
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(),
|
||||
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional()
|
||||
);
|
||||
|
@ -1,24 +1,22 @@
|
||||
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 static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
||||
|
||||
class HsManagedServerBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
|
||||
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;
|
||||
|
||||
class HsManagedServerBookingItemValidator extends HsBookingItemEntityValidator {
|
||||
|
||||
HsManagedServerBookingItemValidator() {
|
||||
super(
|
||||
integerProperty("CPUs").min(1).max(32).required(),
|
||||
integerProperty("RAM").unit("GB").min(1).max(128).required(),
|
||||
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required(),
|
||||
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).optional(),
|
||||
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required(),
|
||||
enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").optional(),
|
||||
booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").optional(),
|
||||
integerProperty("SSD").unit("GB").min(25).max(1000).step(25).required().asTotalLimit().withThreshold(200),
|
||||
integerProperty("HDD").unit("GB").min(0).max(4000).step(250).withDefault(0).asTotalLimit().withThreshold(200),
|
||||
integerProperty("Traffic").unit("GB").min(250).max(10000).step(250).required().asTotalLimit().withThreshold(200),
|
||||
enumerationProperty("SLA-Platform").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC"),
|
||||
booleanProperty("SLA-EMail").falseIf("SLA-Platform", "BASIC").withDefault(false),
|
||||
booleanProperty("SLA-Maria").falseIf("SLA-Platform", "BASIC").optional(),
|
||||
booleanProperty("SLA-PgSQL").falseIf("SLA-Platform", "BASIC").optional(),
|
||||
booleanProperty("SLA-Office").falseIf("SLA-Platform", "BASIC").optional(),
|
||||
|
@ -1,24 +1,98 @@
|
||||
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.IntegerProperty;
|
||||
import org.apache.commons.lang3.function.TriFunction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.validation.BooleanPropertyValidator.booleanProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationPropertyValidator.enumerationProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.DOMAIN_EMAIL_SETUP;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.EMAIL_ADDRESS;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_DATABASE;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MARIADB_USER;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_DATABASE;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.PGSQL_USER;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.UNIX_USER;
|
||||
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;
|
||||
|
||||
class HsManagedWebspaceBookingItemValidator extends HsEntityValidator<HsBookingItemEntity, HsBookingItemType> {
|
||||
class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator {
|
||||
|
||||
public HsManagedWebspaceBookingItemValidator() {
|
||||
super(
|
||||
integerProperty("SSD").unit("GB").min(1).max(100).step(1).required(),
|
||||
integerProperty("HDD").unit("GB").min(0).max(250).step(10).optional(),
|
||||
integerProperty("Traffic").unit("GB").min(10).max(1000).step(10).required(),
|
||||
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").optional(),
|
||||
integerProperty("Daemons").min(0).max(10).optional(),
|
||||
booleanProperty("Online Office Server").optional()
|
||||
integerProperty("Multi").min(1).max(100).step(1).withDefault(1)
|
||||
.eachComprising( 25, unixUsers())
|
||||
.eachComprising( 5, databaseUsers())
|
||||
.eachComprising( 5, databases())
|
||||
.eachComprising(250, eMailAddresses()),
|
||||
integerProperty("Daemons").min(0).max(10).withDefault(0),
|
||||
booleanProperty("Online Office Server").optional(),
|
||||
enumerationProperty("SLA-Platform").values("BASIC", "EXT24H").withDefault("BASIC")
|
||||
);
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> unixUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
final var unixUserCount = entity.getSubHostingAssets().stream()
|
||||
.flatMap(ha -> ha.getSubHostingAssets().stream())
|
||||
.filter(ha -> ha.getType() == UNIX_USER)
|
||||
.count();
|
||||
final long limitingValue = prop.getValue(entity.getResources());
|
||||
if (unixUserCount > factor*limitingValue) {
|
||||
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " unix users, but " + unixUserCount + " found");
|
||||
}
|
||||
return emptyList();
|
||||
};
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databaseUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
final var unixUserCount = entity.getSubHostingAssets().stream()
|
||||
.flatMap(ha -> ha.getSubHostingAssets().stream())
|
||||
.filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER )
|
||||
.count();
|
||||
final long limitingValue = prop.getValue(entity.getResources());
|
||||
if (unixUserCount > factor*limitingValue) {
|
||||
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " database users, but " + unixUserCount + " found");
|
||||
}
|
||||
return emptyList();
|
||||
};
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databases() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
final var unixUserCount = entity.getSubHostingAssets().stream()
|
||||
.flatMap(ha -> ha.getSubHostingAssets().stream())
|
||||
.filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER )
|
||||
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
|
||||
.filter(ha -> ha.getType()==PGSQL_DATABASE || ha.getType()==MARIADB_DATABASE))
|
||||
.count();
|
||||
final long limitingValue = prop.getValue(entity.getResources());
|
||||
if (unixUserCount > factor*limitingValue) {
|
||||
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " databases, but " + unixUserCount + " found");
|
||||
}
|
||||
return emptyList();
|
||||
};
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> eMailAddresses() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
final var unixUserCount = entity.getSubHostingAssets().stream()
|
||||
.flatMap(ha -> ha.getSubHostingAssets().stream())
|
||||
.filter(bi -> bi.getType() == DOMAIN_EMAIL_SETUP)
|
||||
.flatMap(domainEMailSetup -> domainEMailSetup.getSubHostingAssets().stream()
|
||||
.filter(ha -> ha.getType()==EMAIL_ADDRESS))
|
||||
.count();
|
||||
final long limitingValue = prop.getValue(entity.getResources());
|
||||
if (unixUserCount > factor*limitingValue) {
|
||||
return List.of(prop.propertyName() + "=" + limitingValue + " allows at maximum " + limitingValue*factor + " databases, but " + unixUserCount + " found");
|
||||
}
|
||||
return emptyList();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package net.hostsharing.hsadminng.hs.booking.item.validators;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.validation.EnumerationProperty.enumerationProperty;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||
|
||||
class HsPrivateCloudBookingItemValidator extends HsBookingItemEntityValidator {
|
||||
|
||||
HsPrivateCloudBookingItemValidator() {
|
||||
super(
|
||||
integerProperty("CPUs").min(4).max(128).required().asTotalLimit(),
|
||||
integerProperty("RAM").unit("GB").min(4).max(512).required().asTotalLimit(),
|
||||
integerProperty("SSD").unit("GB").min(100).max(4000).step(25).required().asTotalLimit(),
|
||||
integerProperty("HDD").unit("GB").min(0).max(16000).step(25).withDefault(0).asTotalLimit(),
|
||||
integerProperty("Traffic").unit("GB").min(1000).max(40000).step(250).required().asTotalLimit(),
|
||||
enumerationProperty("SLA-Infrastructure").values("BASIC", "EXT8H", "EXT4H", "EXT2H").withDefault("BASIC")
|
||||
);
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators.valid;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry.validated;
|
||||
|
||||
@RestController
|
||||
public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
@ -62,7 +62,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
final var entityToSave = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var saved = assetRepo.save(valid(entityToSave));
|
||||
final var saved = validated(assetRepo.save(entityToSave));
|
||||
|
||||
final var uri =
|
||||
MvcUriComponentsBuilder.fromController(getClass())
|
||||
@ -117,7 +117,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
new HsHostingAssetEntityPatcher(current).apply(body);
|
||||
|
||||
final var saved = assetRepo.save(valid(current));
|
||||
final var saved = validated(assetRepo.save(current));
|
||||
final var mapped = mapper.map(saved, HsHostingAssetResource.class);
|
||||
return ResponseEntity.ok(mapped);
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
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.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||
@ -17,6 +16,7 @@ import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EnumType;
|
||||
@ -25,11 +25,13 @@ import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import jakarta.persistence.Version;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@ -56,7 +58,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validatable<HsHostingAssetEntity, HsHostingAssetType> {
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject {
|
||||
|
||||
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
|
||||
.withProp(HsHostingAssetEntity::getType)
|
||||
@ -91,6 +93,10 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
|
||||
@Enumerated(EnumType.STRING)
|
||||
private HsHostingAssetType type;
|
||||
|
||||
@OneToMany(cascade = CascadeType.REFRESH, orphanRemoval = true)
|
||||
@JoinColumn(name="parentassetuuid", referencedColumnName="uuid")
|
||||
private List<HsHostingAssetEntity> subHostingAssets;
|
||||
|
||||
@Column(name = "identifier")
|
||||
private String identifier; // vm1234, xyz00, example.org, xyz00_abc
|
||||
|
||||
@ -114,16 +120,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject, Validata
|
||||
PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper; }, config).assign(newConfg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPropertiesName() {
|
||||
return "config";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getProperties() {
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidators;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.validators.HsHostingAssetEntityValidatorRegistry;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.api.HsHostingAssetPropsApi;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetTypeResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -15,7 +15,7 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
|
||||
|
||||
@Override
|
||||
public ResponseEntity<List<String>> listAssetTypes() {
|
||||
final var resource = HsHostingAssetEntityValidators.types().stream()
|
||||
final var resource = HsHostingAssetEntityValidatorRegistry.types().stream()
|
||||
.map(Enum::name)
|
||||
.toList();
|
||||
return ResponseEntity.ok(resource);
|
||||
@ -25,7 +25,8 @@ public class HsHostingAssetPropsController implements HsHostingAssetPropsApi {
|
||||
public ResponseEntity<List<Object>> listAssetTypeProps(
|
||||
final HsHostingAssetTypeResource assetType) {
|
||||
|
||||
final var propValidators = HsHostingAssetEntityValidators.forType(HsHostingAssetType.of(assetType));
|
||||
final Enum<HsHostingAssetType> type = HsHostingAssetType.of(assetType);
|
||||
final var propValidators = HsHostingAssetEntityValidatorRegistry.forType(type);
|
||||
final List<Map<String, Object>> resource = propValidators.properties();
|
||||
return ResponseEntity.ok(toListOfObjects(resource));
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
import net.hostsharing.hsadminng.hs.validation.ValidatableProperty;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
public class HsHostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity> {
|
||||
|
||||
public HsHostingAssetEntityValidator(final ValidatableProperty<?>... properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<String> validate(final HsHostingAssetEntity assetEntity) {
|
||||
return sequentiallyValidate(
|
||||
() -> validateProperties(assetEntity),
|
||||
() -> optionallyValidate(assetEntity.getBookingItem()),
|
||||
() -> optionallyValidate(assetEntity.getParentAsset()),
|
||||
() -> validateAgainstSubEntities(assetEntity)
|
||||
);
|
||||
}
|
||||
|
||||
private List<String> validateProperties(final HsHostingAssetEntity assetEntity) {
|
||||
return enrich(prefix(assetEntity.toShortString(), "config"), validateProperties(assetEntity.getConfig()));
|
||||
}
|
||||
|
||||
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
|
||||
return assetEntity != null
|
||||
? enrich(prefix(assetEntity.toShortString(), "parentAsset"),
|
||||
HsHostingAssetEntityValidatorRegistry.forType(assetEntity.getType()).validate(assetEntity))
|
||||
: emptyList();
|
||||
}
|
||||
|
||||
private static List<String> optionallyValidate(final HsBookingItemEntity bookingItem) {
|
||||
return bookingItem != null
|
||||
? enrich(prefix(bookingItem.toShortString(), "bookingItem"),
|
||||
HsBookingItemEntityValidatorRegistry.doValidate(bookingItem))
|
||||
: emptyList();
|
||||
}
|
||||
|
||||
protected List<String> validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) {
|
||||
return enrich(prefix(assetEntity.toShortString(), "config"),
|
||||
stream(propertyValidators)
|
||||
.filter(ValidatableProperty::isTotalsValidator)
|
||||
.map(prop -> validateMaxTotalValue(assetEntity, prop))
|
||||
.filter(Objects::nonNull)
|
||||
.toList());
|
||||
}
|
||||
|
||||
private String validateMaxTotalValue(
|
||||
final HsHostingAssetEntity hostingAsset,
|
||||
final ValidatableProperty<?> propDef) {
|
||||
final var propName = propDef.propertyName();
|
||||
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");
|
||||
final var totalValue = ofNullable(hostingAsset.getSubHostingAssets()).orElse(emptyList())
|
||||
.stream()
|
||||
.map(subItem -> propDef.getValue(subItem.getConfig()))
|
||||
.map(HsEntityValidator::toNonNullInteger)
|
||||
.reduce(0, Integer::sum);
|
||||
final var maxValue = getNonNullIntegerValue(propDef, hostingAsset.getConfig());
|
||||
return totalValue > maxValue
|
||||
? "%s' maximum total is %d%s, but actual total is %s %d%s".formatted(
|
||||
propName, maxValue, propUnit, propName, totalValue, propUnit)
|
||||
: null;
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.*;
|
||||
|
||||
public class HsHostingAssetEntityValidatorRegistry {
|
||||
|
||||
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity>> validators = new HashMap<>();
|
||||
static {
|
||||
register(CLOUD_SERVER, new HsHostingAssetEntityValidator());
|
||||
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
|
||||
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
|
||||
register(UNIX_USER, new HsHostingAssetEntityValidator());
|
||||
}
|
||||
|
||||
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity> validator) {
|
||||
stream(validator.propertyValidators).forEach( entry -> {
|
||||
entry.verifyConsistency(Map.entry(type, validator));
|
||||
});
|
||||
validators.put(type, validator);
|
||||
}
|
||||
|
||||
public static HsEntityValidator<HsHostingAssetEntity> forType(final Enum<HsHostingAssetType> type) {
|
||||
if ( validators.containsKey(type)) {
|
||||
return validators.get(type);
|
||||
}
|
||||
throw new IllegalArgumentException("no validator found for type " + type);
|
||||
}
|
||||
|
||||
public static Set<Enum<HsHostingAssetType>> types() {
|
||||
return validators.keySet();
|
||||
}
|
||||
|
||||
public static List<String> doValidate(final HsHostingAssetEntity hostingAsset) {
|
||||
return HsHostingAssetEntityValidatorRegistry.forType(hostingAsset.getType()).validate(hostingAsset);
|
||||
}
|
||||
|
||||
public static HsHostingAssetEntity validated(final HsHostingAssetEntity entityToSave) {
|
||||
MultiValidationException.throwInvalid(doValidate(entityToSave));
|
||||
return entityToSave;
|
||||
}
|
||||
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Arrays.stream;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.CLOUD_SERVER;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_SERVER;
|
||||
import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.MANAGED_WEBSPACE;
|
||||
|
||||
@UtilityClass
|
||||
public class HsHostingAssetEntityValidators {
|
||||
|
||||
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType>> validators = new HashMap<>();
|
||||
static {
|
||||
register(CLOUD_SERVER, new HsEntityValidator<>());
|
||||
register(MANAGED_SERVER, new HsManagedServerHostingAssetValidator());
|
||||
register(MANAGED_WEBSPACE, new HsManagedWebspaceHostingAssetValidator());
|
||||
}
|
||||
|
||||
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> validator) {
|
||||
stream(validator.propertyValidators).forEach( entry -> {
|
||||
entry.verifyConsistency(Map.entry(type, validator));
|
||||
});
|
||||
validators.put(type, validator);
|
||||
}
|
||||
|
||||
public static HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> forType(final Enum<HsHostingAssetType> type) {
|
||||
return validators.get(type);
|
||||
}
|
||||
|
||||
public static Set<Enum<HsHostingAssetType>> types() {
|
||||
return validators.keySet();
|
||||
}
|
||||
|
||||
|
||||
public static HsHostingAssetEntity valid(final HsHostingAssetEntity entityToSave) {
|
||||
final var violations = HsHostingAssetEntityValidators.forType(entityToSave.getType()).validate(entityToSave);
|
||||
if (!violations.isEmpty()) {
|
||||
throw new ValidationException(violations.toString());
|
||||
}
|
||||
return entityToSave;
|
||||
}
|
||||
}
|
@ -1,12 +1,8 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerProperty.integerProperty;
|
||||
|
||||
import static net.hostsharing.hsadminng.hs.validation.IntegerPropertyValidator.integerProperty;
|
||||
|
||||
class HsManagedServerHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
|
||||
class HsManagedServerHostingAssetValidator extends HsHostingAssetEntityValidator {
|
||||
|
||||
public HsManagedServerHostingAssetValidator() {
|
||||
super(
|
||||
|
@ -1,28 +1,29 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
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.Collection;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
class HsManagedWebspaceHostingAssetValidator extends HsEntityValidator<HsHostingAssetEntity, HsHostingAssetType> {
|
||||
class HsManagedWebspaceHostingAssetValidator extends HsHostingAssetEntityValidator {
|
||||
public HsManagedWebspaceHostingAssetValidator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validate(final HsHostingAssetEntity assetEntity) {
|
||||
final var result = super.validate(assetEntity);
|
||||
validateIdentifierPattern(result, assetEntity);
|
||||
|
||||
return result;
|
||||
return Stream.of(validateIdentifierPattern(assetEntity), super.validate(assetEntity))
|
||||
.flatMap(Collection::stream)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static void validateIdentifierPattern(final List<String> result, final HsHostingAssetEntity assetEntity) {
|
||||
private static List<String> validateIdentifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
final var expectedIdentifierPattern = "^" + assetEntity.getParentAsset().getBookingItem().getProject().getDebitor().getDefaultPrefix() + "[0-9][0-9]$";
|
||||
if ( !assetEntity.getIdentifier().matches(expectedIdentifierPattern)) {
|
||||
result.add("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'");
|
||||
return List.of("'identifier' expected to match '"+expectedIdentifierPattern+"', but is '" + assetEntity.getIdentifier() + "'");
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package net.hostsharing.hsadminng.hs.office.coopassets;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopAssetsApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
|
||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
@ -13,14 +14,12 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static java.lang.String.join;
|
||||
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopAssetsTransactionTypeResource.*;
|
||||
|
||||
@RestController
|
||||
@ -97,9 +96,7 @@ public class HsOfficeCoopAssetsTransactionController implements HsOfficeCoopAsse
|
||||
validateDebitTransaction(requestBody, violations);
|
||||
validateCreditTransaction(requestBody, violations);
|
||||
validateAssetValue(requestBody, violations);
|
||||
if (violations.size() > 0) {
|
||||
throw new ValidationException("[" + join(", ", violations) + "]");
|
||||
}
|
||||
MultiValidationException.throwInvalid(violations);
|
||||
}
|
||||
|
||||
private static void validateDebitTransaction(
|
||||
|
@ -5,6 +5,7 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeCoopSharesApi;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionInsertResource;
|
||||
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionResource;
|
||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
import net.hostsharing.hsadminng.mapper.Mapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
@ -14,14 +15,12 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
|
||||
import jakarta.validation.ValidationException;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static java.lang.String.join;
|
||||
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.CANCELLATION;
|
||||
import static net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficeCoopSharesTransactionTypeResource.SUBSCRIPTION;
|
||||
|
||||
@ -99,9 +98,7 @@ public class HsOfficeCoopSharesTransactionController implements HsOfficeCoopShar
|
||||
validateSubscriptionTransaction(requestBody, violations);
|
||||
validateCancellationTransaction(requestBody, violations);
|
||||
validateshareCount(requestBody, violations);
|
||||
if (violations.size() > 0) {
|
||||
throw new ValidationException("[" + join(", ", violations) + "]");
|
||||
}
|
||||
MultiValidationException.throwInvalid(violations);
|
||||
}
|
||||
|
||||
private static void validateSubscriptionTransaction(
|
||||
|
@ -0,0 +1,46 @@
|
||||
package net.hostsharing.hsadminng.hs.validation;
|
||||
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.mapper.Array;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Setter
|
||||
public class BooleanProperty extends ValidatableProperty<Boolean> {
|
||||
|
||||
private static final String[] KEY_ORDER = Array.join(ValidatableProperty.KEY_ORDER_HEAD, ValidatableProperty.KEY_ORDER_TAIL);
|
||||
|
||||
private Map.Entry<String, String> falseIf;
|
||||
|
||||
private BooleanProperty(final String propertyName) {
|
||||
super(Boolean.class, propertyName, KEY_ORDER);
|
||||
}
|
||||
|
||||
public static BooleanProperty booleanProperty(final String propertyName) {
|
||||
return new BooleanProperty(propertyName);
|
||||
}
|
||||
|
||||
public ValidatableProperty<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) {
|
||||
this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validate(final ArrayList<String> result, final Boolean propValue, final Map<String, Object> props) {
|
||||
if (falseIf != null && propValue) {
|
||||
final Object referencedValue = props.get(falseIf.getKey());
|
||||
if (Objects.equals(referencedValue, falseIf.getValue())) {
|
||||
result.add(propertyName + "' is expected to be false because " +
|
||||
falseIf.getKey() + "=" + referencedValue + " but is " + propValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String simpleTypeName() {
|
||||
return "boolean";
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package net.hostsharing.hsadminng.hs.validation;
|
||||
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@Setter
|
||||
public class BooleanPropertyValidator extends HsPropertyValidator<Boolean> {
|
||||
|
||||
private Map.Entry<String, String> falseIf;
|
||||
|
||||
private BooleanPropertyValidator(final String propertyName) {
|
||||
super(Boolean.class, propertyName);
|
||||
}
|
||||
|
||||
public static BooleanPropertyValidator booleanProperty(final String propertyName) {
|
||||
return new BooleanPropertyValidator(propertyName);
|
||||
}
|
||||
|
||||
public HsPropertyValidator<Boolean> falseIf(final String refPropertyName, final String refPropertyValue) {
|
||||
this.falseIf = new AbstractMap.SimpleImmutableEntry<>(refPropertyName, refPropertyValue);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validate(final ArrayList<String> result, final String propertiesName, final Boolean propValue, final Map<String, Object> props) {
|
||||
if (falseIf != null && !Objects.equals(props.get(falseIf.getKey()), falseIf.getValue())) {
|
||||
if (propValue) {
|
||||
result.add("'"+propertiesName+"." + propertyName + "' is expected to be false because " +
|
||||
propertiesName+"." + falseIf.getKey()+ "=" + falseIf.getValue() + " but is " + propValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String simpleTypeName() {
|
||||
return "boolean";
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package net.hostsharing.hsadminng.hs.validation;
|
||||
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.mapper.Array;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
@Setter
|
||||
public class EnumerationProperty extends ValidatableProperty<String> {
|
||||
|
||||
private static final String[] KEY_ORDER = Array.join(
|
||||
ValidatableProperty.KEY_ORDER_HEAD,
|
||||
Array.of("values"),
|
||||
ValidatableProperty.KEY_ORDER_TAIL);
|
||||
|
||||
private String[] values;
|
||||
|
||||
private EnumerationProperty(final String propertyName) {
|
||||
super(String.class, propertyName, KEY_ORDER);
|
||||
}
|
||||
|
||||
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 + "'");
|
||||
}
|
||||