HostingAssetEntitySaveProcessor.java
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
import net.hostsharing.hsadminng.errors.MultiValidationException;
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
import jakarta.persistence.EntityManager;
import java.util.Arrays;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
/**
* Wraps the steps of the pararation, validation, mapping and revamp around saving of a HsHostingAsset into a readable API.
*/
public class HostingAssetEntitySaveProcessor {
private final HsEntityValidator<HsHostingAsset> validator;
private String expectedStep = "preprocessEntity";
private final EntityManager em;
private HsHostingAsset entity;
private HsHostingAssetResource resource;
public HostingAssetEntitySaveProcessor(final EntityManager em, final HsHostingAsset entity) {
this.em = em;
this.entity = entity;
this.validator = HostingAssetEntityValidatorRegistry.forType(entity.getType());
}
/// initial step allowing to set default values before any validations
public HostingAssetEntitySaveProcessor preprocessEntity() {
step("preprocessEntity", "validateEntity");
validator.preprocessEntity(entity);
return this;
}
/// validates the entity itself including its properties
public HostingAssetEntitySaveProcessor validateEntity() {
step("validateEntity", "prepareForSave");
MultiValidationException.throwIfNotEmpty(validator.validateEntity(entity));
return this;
}
// TODO.legacy: remove once the migration of legacy data is done
/// validates the entity itself including its properties, but ignoring some error messages for import of legacy data
public HostingAssetEntitySaveProcessor validateEntityIgnoring(final String... ignoreRegExp) {
step("validateEntity", "prepareForSave");
final var ignoreRegExpPatterns = Arrays.stream(ignoreRegExp).map(Pattern::compile).toList();
MultiValidationException.throwIfNotEmpty(
validator.validateEntity(entity).stream()
.filter(error -> ignoreRegExpPatterns.stream().noneMatch(p -> p.matcher(error).matches() ))
.toList()
);
return this;
}
/// hashing passwords etc.
@SuppressWarnings("unchecked")
public HostingAssetEntitySaveProcessor prepareForSave() {
step("prepareForSave", "save");
validator.prepareProperties(em, entity);
return this;
}
/**
* Saves the entity using the given `saveFunction`.
*
* <p>`validator.postPersist(em, entity)` is NOT called.
* If any postprocessing is necessary, the saveFunction has to implement this.</p>
* @param saveFunction
* @return
*/
public HostingAssetEntitySaveProcessor saveUsing(final Function<HsHostingAsset, HsHostingAsset> saveFunction) {
step("save", "validateContext");
entity = saveFunction.apply(entity);
return this;
}
/**
* Saves the using the `EntityManager`, but does NOT ever merge the entity.
*
* <p>`validator.postPersist(em, entity)` is called afterwards with the entity guaranteed to be flushed to the database.</p>
* @return
*/
public HostingAssetEntitySaveProcessor save() {
return saveUsing(e -> {
if (!em.contains(entity)) {
em.persist(entity);
}
em.flush(); // makes RbacEntity available as RealEntity if needed
validator.postPersist(em, entity);
return entity;
});
}
/// validates the entity within it's parent and child hierarchy (e.g. totals validators and other limits)
public HostingAssetEntitySaveProcessor validateContext() {
step("validateContext", "mapUsing");
return HsEntityValidator.doWithEntityManager(em, () -> {
MultiValidationException.throwIfNotEmpty(validator.validateContext(entity));
return this;
});
}
/// maps entity to JSON resource representation
public HostingAssetEntitySaveProcessor mapUsing(
final Function<HsHostingAsset, HsHostingAssetResource> mapFunction) {
step("mapUsing", "revampProperties");
resource = mapFunction.apply(entity);
return this;
}
/// removes write-only-properties and ads computed-properties
@SuppressWarnings("unchecked")
public HsHostingAssetResource revampProperties() {
step("revampProperties", null);
final var revampedProps = validator.revampProperties(em, entity, (Map<String, Object>) resource.getConfig());
resource.setConfig(revampedProps);
return resource;
}
// Makes sure that the steps are called in the correct order.
// Could also be implemented using an interface per method, but that seems exaggerated.
private void step(final String current, final String next) {
if (!expectedStep.equals(current)) {
throw new IllegalStateException("expected " + expectedStep + " but got " + current);
}
expectedStep = next;
}
}