import-unix-user-and-email-aliases (#81)
Co-authored-by: Michael Hoennig <michael@hoennig.de> Reviewed-on: #81 Reviewed-by: Marc Sandlus <marc.sandlus@hostsharing.net>
This commit is contained in:
parent
e1fda412ae
commit
d6a0511d98
@ -1,13 +1,16 @@
|
||||
# RBAC Performance Analysis
|
||||
|
||||
This describes the analysis of the legacy-data-import which took way too long, which turned out to be a problem in the RBAC-access-rights-check.
|
||||
This describes the analysis of the legacy-data-import which took way too long, which turned out to be a problem in the RBAC-access-rights-check as well as `EntityManager.persist` creating too many SQL queries.
|
||||
|
||||
|
||||
## Our Performance-Problem
|
||||
|
||||
During the legacy data import for hosting assets we noticed massive performance problems. The import of about 2200 hosting-assets (IP-numbers, managed-webspaces, managed- and cloud-servers) as well as the creation of booking-items and booking-projects as well as necessary office-data entities (persons, contacts, partners, debitors, relations) **took 10-25 minutes**.
|
||||
During the legacy data import for hosting assets we noticed massive performance problems. The import of about 2200 hosting-assets (IP-numbers, managed-webspaces, managed- and cloud-servers) as well as the creation of booking-items and booking-projects as well as necessary office-data entities (persons, contacts, partners, debitors, relations) **took 25 minutes**.
|
||||
|
||||
We could not find a pattern, why the import mostly took about 25 minutes, but sometimes took *just* 10 minutes. The impression that it had to do with too many other parallel processes, e.g. browser with BBB or IntelliJ IDEA was proved wrong, but stopping all unnecessary processes and performing the import again.
|
||||
Importing hosting assets up to UnixUsers and EmailAddresses even **took about 100 minutes**.
|
||||
|
||||
(The office data import sometimes, but rarely, took only 10min.
|
||||
We could not find a pattern, why that was the case. The impression that it had to do with too many other parallel processes, e.g. browser with BBB or IntelliJ IDEA was proved wrong, but stopping all unnecessary processes and performing the import again.)
|
||||
|
||||
|
||||
## Preparation
|
||||
@ -111,7 +114,23 @@ time gw-importHostingAssets
|
||||
|
||||
### Fetch the Query Statistics
|
||||
|
||||
And afterward we can query the statistics in PostgreSQL:
|
||||
And afterward we can query the statistics in PostgreSQL, e.g.:
|
||||
|
||||
```SQL
|
||||
WITH statements AS (
|
||||
SELECT * FROM pg_stat_statements pss
|
||||
)
|
||||
SELECT calls,
|
||||
total_exec_time::int/(60*1000) as total_exec_time_mins,
|
||||
mean_exec_time::int as mean_exec_time_millis,
|
||||
query
|
||||
FROM statements
|
||||
WHERE calls > 100 AND shared_blks_hit > 0
|
||||
ORDER BY total_exec_time_mins DESC
|
||||
LIMIT 16;
|
||||
```
|
||||
|
||||
### Reset the Query Statistics
|
||||
|
||||
```SQL
|
||||
SELECT pg_stat_statements_reset();
|
||||
@ -272,6 +291,7 @@ The slowest query now was fetching Relations joined with Contact, Anchor-Person
|
||||
|
||||
We changed these mappings from `EAGER` (default) to `LAZY` to `@ManyToOne(fetch = FetchType.LAZY)` and got this result:
|
||||
|
||||
:::small
|
||||
| query | calls | total (min) | mean (ms) |
|
||||
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|-------------|----------|
|
||||
| select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office_person_rv hope1_0 where hope1_0.uuid=$1 | 1015 | 4 | 238 |
|
||||
@ -292,10 +312,69 @@ We changed these mappings from `EAGER` (default) to `LAZY` to `@ManyToOne(fetch
|
||||
|
||||
Now, finally, the total runtime of the import was down to 12 minutes. This is repeatable, where originally, the import took about 25mins in most cases and just rarely - and for unknown reasons - 10min.
|
||||
|
||||
### Importing UnixUser and EmailAlias Assets
|
||||
|
||||
But once UnixUser and EmailAlias assets got added to the import, the total time went up to about 110min.
|
||||
|
||||
This was not acceptable, especially not, considering that domains, email-addresses and database-assets are almost 10 times that number and thus the import would go up to over 1100min which is 20 hours.
|
||||
|
||||
In a first step, a `HsHostingAssetRawEntity` was created, mapped to the raw table (hs_hosting_asset) not to the RBAC-view (hs_hosting_asset_rv). Unfortunately we did not keep measurements, but that was only part of the problem anyway.
|
||||
|
||||
The main problem was, that there is something strange with persisting (`EntityManager.persist`) for EmailAlias assets. Where importing UnixUsers was mostly slow due to RBAC SELECT-permission checks, persisting EmailAliases suddenly created about a million (in numbers 1.000.000) SQL UPDATE statements after the INSERT, all with the same data, just increased version number (used for optimistic locking). We were not able to figure out why this happened.
|
||||
|
||||
Keep in mind, it's the same table with the same RBAC-triggers, just a different value in the type column.
|
||||
|
||||
Once `EntityManager.persist` was replaced by an explicit SQL INSERT - just for `HsHostingAssetRawEntity`, the total time was down to 17min. Thus importing the UnixUsers and EmailAliases took just 5min, which is an acceptable result. The total import of all HostingAssets is now estimated to about 1 hour (on my developer laptop).
|
||||
|
||||
Now, the longest running queries are these:
|
||||
|
||||
| No.| calls | total_m | mean_ms | query |
|
||||
|---:|---------|--------:|--------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 1 | 13.093 | 4 | 21 | insert into hs_hosting_asset( uuid, type, bookingitemuuid, parentassetuuid, assignedtoassetuuid, alarmcontactuuid, identifier, caption, config, version) values ( $1, $2, $3, $4, $5, $6, $7, $8, cast($9 as jsonb), $10) |
|
||||
| 2 | 517 | 4 | 502 | select hore1_0.uuid,hore1_0.anchoruuid,hore1_0.contactuuid,hore1_0.holderuuid,hore1_0.mark,hore1_0.type,hore1_0.version from public.hs_office_relation_rv hore1_0 where hore1_0.uuid=$1 |
|
||||
| 3 | 13.144 | 4 | 21 | call buildRbacSystemForHsHostingAsset(NEW) |
|
||||
| 4 | 96.632 | 3 | 2 | call grantRoleToRole(roleUuid, superRoleUuid, superRoleDesc.assumed) |
|
||||
| 5 | 120.815 | 3 | 2 | select * from isGranted(array[granteeId], grantedId) |
|
||||
| 6 | 123.740 | 3 | 2 | with recursive grants as ( select descendantUuid, ascendantUuid from RbacGrants where descendantUuid = grantedId union all select "grant".descendantUuid, "grant".ascendantUuid from RbacGrants "grant" inner join grants recur on recur.ascendantUuid = "grant".descendantUuid ) select exists ( select $3 from grants where ascendantUuid = any(granteeIds) ) or grantedId = any(granteeIds) |
|
||||
| 7 | 497 | 2 | 259 | select hoce1_0.uuid,hoce1_0.caption,hoce1_0.emailaddresses,hoce1_0.phonenumbers,hoce1_0.postaladdress,hoce1_0.version from public.hs_office_contact_rv hoce1_0 where hoce1_0.uuid=$1 |
|
||||
| 8 | 497 | 2 | 255 | select hope1_0.uuid,hope1_0.familyname,hope1_0.givenname,hope1_0.persontype,hope1_0.salutation,hope1_0.title,hope1_0.tradename,hope1_0.version from public.hs_office_person_rv hope1_0 where hope1_0.uuid=$1 |
|
||||
| 9 | 13.144 | 1 | 8 | SELECT createRoleWithGrants( hsHostingAssetTENANT(NEW), permissions => array[$7], incomingSuperRoles => array[ hsHostingAssetAGENT(NEW), hsOfficeContactADMIN(newAlarmContact)], outgoingSubRoles => array[ hsBookingItemTENANT(newBookingItem), hsHostingAssetTENANT(newParentAsset)] ) |
|
||||
| 10 | 13.144 | 1 | 5 | SELECT createRoleWithGrants( hsHostingAssetADMIN(NEW), permissions => array[$7], incomingSuperRoles => array[ hsBookingItemAGENT(newBookingItem), hsHostingAssetAGENT(newParentAsset), hsHostingAssetOWNER(NEW)] ) |
|
||||
|
||||
That the `INSERT into hs_hosting_asset` (No. 1) takes up the most time, seems to be normal, and 21ms for each call is also fine.
|
||||
|
||||
It seems that the trigger effects (eg. No. 3 and No. 4) are included in the measure for the causing INSERT, otherwise summing up the totals would exceed the actual total time of the whole import. And it was to be expected that building the RBAC rules for new business objects takes most of the time.
|
||||
|
||||
In production, the `SELECT ... FROM hs_office_relation_rv` (No. 2) with about 0.5 seconds could still be a problem. But once we apply the improvements from the hosting asset area also to the office area, this should not be a problem for the import anymore.
|
||||
|
||||
|
||||
## Further Options To Explore
|
||||
|
||||
1. Instead of separate SQL INSERT statements, we could try bulk INSERT.
|
||||
2. We could use the SQL INSERT method for all entity-classes, or at least for all which have high row counts.
|
||||
3. For the production code, we could use raw-entities for referenced entities, here usually RBAC SELECT permission is given anyway.
|
||||
|
||||
|
||||
## Summary
|
||||
|
||||
That the import runtime is down to about 12min is repeatable, where originally, the import took about 25mins in most cases and just rarely - and for unknown reasons - just 10min.
|
||||
### What we did Achieve?
|
||||
|
||||
In a first step, the total import runtime for office entities was reduced from about 25min to about 10min.
|
||||
|
||||
In a second step, we reduced the import of booking- and hosting-assets from about 100min (not counting the required office entities) to 5min.
|
||||
|
||||
### What Helped?
|
||||
|
||||
Merging the recursive CTE query to determine the RBAC SELECT-permission, made it more clear which business-queries take the time.
|
||||
|
||||
Avoiding EAGER-loading where not neccessary, reduced the total runtime of the import to about the half.
|
||||
Avoiding EAGER-loading where not necessary, reduced the total runtime of the import to about the half.
|
||||
|
||||
The major improvement came from using direct INSERT statements, which then also bypassed the RBAC SELECT permission checks.
|
||||
|
||||
### What Still Has To Be Done?
|
||||
|
||||
Where this performance analysis was mostly helping the performance of the legacy data import, we still need measures and improvements for the productive code.
|
||||
|
||||
For sure, using more LAZY-loading also helps in the production code. For some more ideas see section _Further Options To Explore_.
|
||||
|
||||
|
||||
|
@ -32,6 +32,7 @@ import jakarta.persistence.JoinColumn;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
import jakarta.persistence.OneToMany;
|
||||
import jakarta.persistence.OneToOne;
|
||||
import jakarta.persistence.PostLoad;
|
||||
import jakarta.persistence.Table;
|
||||
import jakarta.persistence.Transient;
|
||||
import jakarta.persistence.Version;
|
||||
@ -124,6 +125,14 @@ public class HsBookingItemEntity implements Stringifyable, RbacObject<HsBookingI
|
||||
@Transient
|
||||
private PatchableMapWrapper<Object> resourcesWrapper;
|
||||
|
||||
@Transient
|
||||
private boolean isLoaded;
|
||||
|
||||
@PostLoad
|
||||
public void markAsLoaded() {
|
||||
this.isLoaded = true;
|
||||
}
|
||||
|
||||
public PatchableMapWrapper<Object> getResources() {
|
||||
return PatchableMapWrapper.of(resourcesWrapper, (newWrapper) -> {resourcesWrapper = newWrapper; }, resources );
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
|
||||
);
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> unixUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> unixUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
|
||||
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
|
||||
.map(ha -> ha.getSubHostingAssets().stream()
|
||||
.filter(subAsset -> subAsset.getType() == UNIX_USER)
|
||||
@ -53,8 +53,8 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
|
||||
};
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databaseUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> databaseUsers() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
|
||||
final var dbUserCount = ofNullable(entity.getRelatedHostingAsset())
|
||||
.map(ha -> ha.getSubHostingAssets().stream()
|
||||
.filter(bi -> bi.getType() == PGSQL_USER || bi.getType() == MARIADB_USER )
|
||||
@ -68,8 +68,8 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
|
||||
};
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> databases() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> databases() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
|
||||
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
|
||||
.map(ha -> ha.getSubHostingAssets().stream()
|
||||
.filter(bi -> bi.getType()==PGSQL_USER || bi.getType()==MARIADB_USER )
|
||||
@ -85,8 +85,8 @@ class HsManagedWebspaceBookingItemValidator extends HsBookingItemEntityValidator
|
||||
};
|
||||
}
|
||||
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty, Integer, List<String>> eMailAddresses() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty prop, final Integer factor) -> {
|
||||
private static TriFunction<HsBookingItemEntity, IntegerProperty<?>, Integer, List<String>> eMailAddresses() {
|
||||
return (final HsBookingItemEntity entity, final IntegerProperty<?> prop, final Integer factor) -> {
|
||||
final var unixUserCount = ofNullable(entity.getRelatedHostingAsset())
|
||||
.map(ha -> ha.getSubHostingAssets().stream()
|
||||
.filter(bi -> bi.getType() == DOMAIN_MBOX_SETUP)
|
||||
|
@ -0,0 +1,72 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
|
||||
public interface HsHostingAsset extends Stringifyable, RbacObject<HsHostingAsset>, PropertiesProvider {
|
||||
|
||||
Stringify<HsHostingAsset> stringify = stringify(HsHostingAsset.class)
|
||||
.withProp(HsHostingAsset::getType)
|
||||
.withProp(HsHostingAsset::getIdentifier)
|
||||
.withProp(HsHostingAsset::getCaption)
|
||||
.withProp(HsHostingAsset::getParentAsset)
|
||||
.withProp(HsHostingAsset::getAssignedToAsset)
|
||||
.withProp(HsHostingAsset::getBookingItem)
|
||||
.withProp(HsHostingAsset::getConfig)
|
||||
.quotedValues(false);
|
||||
|
||||
|
||||
void setUuid(UUID uuid);
|
||||
HsHostingAssetType getType();
|
||||
HsHostingAsset getParentAsset();
|
||||
void setIdentifier(String s);
|
||||
String getIdentifier();
|
||||
HsBookingItemEntity getBookingItem();
|
||||
HsHostingAsset getAssignedToAsset();
|
||||
HsOfficeContactEntity getAlarmContact();
|
||||
List<? extends HsHostingAsset> getSubHostingAssets();
|
||||
String getCaption();
|
||||
Map<String, Object> getConfig();
|
||||
|
||||
default HsBookingProjectEntity getRelatedProject() {
|
||||
return Optional.ofNullable(getBookingItem())
|
||||
.map(HsBookingItemEntity::getRelatedProject)
|
||||
.orElseGet(() -> Optional.ofNullable(getParentAsset())
|
||||
.map(HsHostingAsset::getRelatedProject)
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
@Override
|
||||
default Object getContextValue(final String propName) {
|
||||
final var v = directProps().get(propName);
|
||||
if (v != null) {
|
||||
return v;
|
||||
}
|
||||
|
||||
if (getBookingItem() != null) {
|
||||
return getBookingItem().getResources().get(propName);
|
||||
}
|
||||
if (getParentAsset() != null && getParentAsset().getBookingItem() != null) {
|
||||
return getParentAsset().getBookingItem().getResources().get(propName);
|
||||
}
|
||||
return emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
default String toShortString() {
|
||||
return getType() + ":" + getIdentifier();
|
||||
}
|
||||
}
|
@ -72,7 +72,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
final var entity = mapper.map(body, HsHostingAssetEntity.class, RESOURCE_TO_ENTITY_POSTMAPPER);
|
||||
|
||||
final var mapped = new HostingAssetEntitySaveProcessor(entity)
|
||||
final var mapped = new HostingAssetEntitySaveProcessor(em, entity)
|
||||
.preprocessEntity()
|
||||
.validateEntity()
|
||||
.prepareForSave()
|
||||
@ -133,7 +133,7 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
|
||||
new HsHostingAssetEntityPatcher(em, entity).apply(body);
|
||||
|
||||
final var mapped = new HostingAssetEntitySaveProcessor(entity)
|
||||
final var mapped = new HostingAssetEntitySaveProcessor(em, entity)
|
||||
.preprocessEntity()
|
||||
.validateEntity()
|
||||
.prepareForSave()
|
||||
@ -162,5 +162,5 @@ public class HsHostingAssetController implements HsHostingAssetsApi {
|
||||
@SuppressWarnings("unchecked")
|
||||
final BiConsumer<HsHostingAssetEntity, HsHostingAssetResource> ENTITY_TO_RESOURCE_POSTMAPPER = (entity, resource)
|
||||
-> resource.setConfig(HostingAssetEntityValidatorRegistry.forType(entity.getType())
|
||||
.revampProperties(entity, (Map<String, Object>) resource.getConfig()));
|
||||
.revampProperties(em, entity, (Map<String, Object>) resource.getConfig()));
|
||||
}
|
||||
|
@ -8,15 +8,10 @@ import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.project.HsBookingProjectEntity;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.PropertiesProvider;
|
||||
import net.hostsharing.hsadminng.mapper.PatchableMapWrapper;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView;
|
||||
import net.hostsharing.hsadminng.rbac.rbacdef.RbacView.SQL;
|
||||
import net.hostsharing.hsadminng.rbac.rbacobject.RbacObject;
|
||||
import net.hostsharing.hsadminng.stringify.Stringify;
|
||||
import net.hostsharing.hsadminng.stringify.Stringifyable;
|
||||
import org.hibernate.annotations.Type;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
@ -39,10 +34,8 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.CaseDef.inCaseOf;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.Column.dependsOnColumn;
|
||||
import static net.hostsharing.hsadminng.rbac.rbacdef.RbacView.ColumnValue.usingDefaultCase;
|
||||
@ -70,17 +63,7 @@ import static net.hostsharing.hsadminng.stringify.Stringify.stringify;
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class HsHostingAssetEntity implements Stringifyable, RbacObject<HsHostingAssetEntity>, PropertiesProvider {
|
||||
|
||||
private static Stringify<HsHostingAssetEntity> stringify = stringify(HsHostingAssetEntity.class)
|
||||
.withProp(HsHostingAssetEntity::getType)
|
||||
.withProp(HsHostingAssetEntity::getIdentifier)
|
||||
.withProp(HsHostingAssetEntity::getCaption)
|
||||
.withProp(HsHostingAssetEntity::getParentAsset)
|
||||
.withProp(HsHostingAssetEntity::getAssignedToAsset)
|
||||
.withProp(HsHostingAssetEntity::getBookingItem)
|
||||
.withProp(HsHostingAssetEntity::getConfig)
|
||||
.quotedValues(false);
|
||||
public class HsHostingAssetEntity implements HsHostingAsset {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
@ -136,14 +119,6 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject<HsHosting
|
||||
this.isLoaded = true;
|
||||
}
|
||||
|
||||
public HsBookingProjectEntity getRelatedProject() {
|
||||
return Optional.ofNullable(bookingItem)
|
||||
.map(HsBookingItemEntity::getRelatedProject)
|
||||
.orElseGet(() -> Optional.ofNullable(parentAsset)
|
||||
.map(HsHostingAssetEntity::getRelatedProject)
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
public PatchableMapWrapper<Object> getConfig() {
|
||||
return PatchableMapWrapper.of(configWrapper, (newWrapper) -> {configWrapper = newWrapper;}, config);
|
||||
}
|
||||
@ -157,30 +132,9 @@ public class HsHostingAssetEntity implements Stringifyable, RbacObject<HsHosting
|
||||
return config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getContextValue(final String propName) {
|
||||
final var v = config.get(propName);
|
||||
if (v != null) {
|
||||
return v;
|
||||
}
|
||||
|
||||
if (bookingItem != null) {
|
||||
return bookingItem.getResources().get(propName);
|
||||
}
|
||||
if (parentAsset != null && parentAsset.getBookingItem() != null) {
|
||||
return parentAsset.getBookingItem().getResources().get(propName);
|
||||
}
|
||||
return emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stringify.apply(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toShortString() {
|
||||
return type + ":" + identifier;
|
||||
return stringify.using(HsHostingAssetEntity.class).apply(this);
|
||||
}
|
||||
|
||||
public static RbacView rbac() {
|
||||
|
@ -39,7 +39,7 @@ public interface HsHostingAssetRepository extends Repository<HsHostingAssetEntit
|
||||
return findAllByCriteriaImpl(projectUuid, parentAssetUuid, HsHostingAssetType.asString(type));
|
||||
}
|
||||
|
||||
HsHostingAssetEntity save(HsHostingAssetEntity current);
|
||||
HsHostingAssetEntity save(HsHostingAsset current);
|
||||
|
||||
int deleteByUuid(final UUID uuid);
|
||||
|
||||
|
@ -384,29 +384,29 @@ class EntityTypeRelation<E, T extends Node> {
|
||||
" *==> ");
|
||||
}
|
||||
|
||||
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> optionalParent(final HsHostingAssetType hostingAssetType) {
|
||||
static EntityTypeRelation<HsHostingAsset, HsHostingAssetType> optionalParent(final HsHostingAssetType hostingAssetType) {
|
||||
return new EntityTypeRelation<>(
|
||||
OPTIONAL,
|
||||
PARENT_ASSET,
|
||||
HsHostingAssetEntity::getParentAsset,
|
||||
HsHostingAsset::getParentAsset,
|
||||
hostingAssetType,
|
||||
" o..> ");
|
||||
}
|
||||
|
||||
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> requiredParent(final HsHostingAssetType hostingAssetType) {
|
||||
static EntityTypeRelation<HsHostingAsset, HsHostingAssetType> requiredParent(final HsHostingAssetType hostingAssetType) {
|
||||
return new EntityTypeRelation<>(
|
||||
REQUIRED,
|
||||
PARENT_ASSET,
|
||||
HsHostingAssetEntity::getParentAsset,
|
||||
HsHostingAsset::getParentAsset,
|
||||
hostingAssetType,
|
||||
" *==> ");
|
||||
}
|
||||
|
||||
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> assignedTo(final HsHostingAssetType hostingAssetType) {
|
||||
static EntityTypeRelation<HsHostingAsset, HsHostingAssetType> assignedTo(final HsHostingAssetType hostingAssetType) {
|
||||
return new EntityTypeRelation<>(
|
||||
REQUIRED,
|
||||
ASSIGNED_TO_ASSET,
|
||||
HsHostingAssetEntity::getAssignedToAsset,
|
||||
HsHostingAsset::getAssignedToAsset,
|
||||
hostingAssetType,
|
||||
" o--> ");
|
||||
}
|
||||
@ -416,11 +416,11 @@ class EntityTypeRelation<E, T extends Node> {
|
||||
return this;
|
||||
}
|
||||
|
||||
static EntityTypeRelation<HsHostingAssetEntity, HsHostingAssetType> optionallyAssignedTo(final HsHostingAssetType hostingAssetType) {
|
||||
static EntityTypeRelation<HsHostingAsset, HsHostingAssetType> optionallyAssignedTo(final HsHostingAssetType hostingAssetType) {
|
||||
return new EntityTypeRelation<>(
|
||||
OPTIONAL,
|
||||
ASSIGNED_TO_ASSET,
|
||||
HsHostingAssetEntity::getAssignedToAsset,
|
||||
HsHostingAsset::getAssignedToAsset,
|
||||
hostingAssetType,
|
||||
" o..> ");
|
||||
}
|
||||
|
@ -1,24 +1,27 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.errors.MultiValidationException;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
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.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Wraps the steps of the pararation, validation, mapping and revamp around saving of a HsHostingAssetEntity into a readable API.
|
||||
* 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<HsHostingAssetEntity> validator;
|
||||
private final HsEntityValidator<HsHostingAsset> validator;
|
||||
private String expectedStep = "preprocessEntity";
|
||||
private HsHostingAssetEntity entity;
|
||||
private final EntityManager em;
|
||||
private HsHostingAsset entity;
|
||||
private HsHostingAssetResource resource;
|
||||
|
||||
public HostingAssetEntitySaveProcessor(final HsHostingAssetEntity entity) {
|
||||
public HostingAssetEntitySaveProcessor(final EntityManager em, final HsHostingAsset entity) {
|
||||
this.em = em;
|
||||
this.entity = entity;
|
||||
this.validator = HostingAssetEntityValidatorRegistry.forType(entity.getType());
|
||||
}
|
||||
@ -37,15 +40,26 @@ public class HostingAssetEntitySaveProcessor {
|
||||
return this;
|
||||
}
|
||||
|
||||
/// 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");
|
||||
MultiValidationException.throwIfNotEmpty(
|
||||
validator.validateEntity(entity).stream()
|
||||
.filter(errorMsg -> !errorMsg.matches(ignoreRegExp))
|
||||
.toList()
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// hashing passwords etc.
|
||||
@SuppressWarnings("unchecked")
|
||||
public HostingAssetEntitySaveProcessor prepareForSave() {
|
||||
step("prepareForSave", "saveUsing");
|
||||
validator.prepareProperties(entity);
|
||||
validator.prepareProperties(em, entity);
|
||||
return this;
|
||||
}
|
||||
|
||||
public HostingAssetEntitySaveProcessor saveUsing(final Function<HsHostingAssetEntity, HsHostingAssetEntity> saveFunction) {
|
||||
public HostingAssetEntitySaveProcessor saveUsing(final Function<HsHostingAsset, HsHostingAsset> saveFunction) {
|
||||
step("saveUsing", "validateContext");
|
||||
entity = saveFunction.apply(entity);
|
||||
return this;
|
||||
@ -60,7 +74,7 @@ public class HostingAssetEntitySaveProcessor {
|
||||
|
||||
/// maps entity to JSON resource representation
|
||||
public HostingAssetEntitySaveProcessor mapUsing(
|
||||
final Function<HsHostingAssetEntity, HsHostingAssetResource> mapFunction) {
|
||||
final Function<HsHostingAsset, HsHostingAssetResource> mapFunction) {
|
||||
step("mapUsing", "revampProperties");
|
||||
resource = mapFunction.apply(entity);
|
||||
return this;
|
||||
@ -70,7 +84,7 @@ public class HostingAssetEntitySaveProcessor {
|
||||
@SuppressWarnings("unchecked")
|
||||
public HsHostingAssetResource revampProperties() {
|
||||
step("revampProperties", null);
|
||||
final var revampedProps = validator.revampProperties(entity, (Map<String, Object>) resource.getConfig());
|
||||
final var revampedProps = validator.revampProperties(em, entity, (Map<String, Object>) resource.getConfig());
|
||||
resource.setConfig(revampedProps);
|
||||
return resource;
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemEntity;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.HsBookingItemType;
|
||||
import net.hostsharing.hsadminng.hs.booking.item.validators.HsBookingItemEntityValidatorRegistry;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactEntity;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
@ -23,13 +23,13 @@ import static java.util.Arrays.stream;
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Optional.ofNullable;
|
||||
|
||||
public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHostingAssetEntity> {
|
||||
public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHostingAsset> {
|
||||
|
||||
static final ValidatableProperty<?, ?>[] NO_EXTRA_PROPERTIES = new ValidatableProperty<?, ?>[0];
|
||||
|
||||
private final ReferenceValidator<HsBookingItemEntity, HsBookingItemType> bookingItemReferenceValidation;
|
||||
private final ReferenceValidator<HsHostingAssetEntity, HsHostingAssetType> parentAssetReferenceValidation;
|
||||
private final ReferenceValidator<HsHostingAssetEntity, HsHostingAssetType> assignedToAssetReferenceValidation;
|
||||
private final ReferenceValidator<HsHostingAsset, HsHostingAssetType> parentAssetReferenceValidation;
|
||||
private final ReferenceValidator<HsHostingAsset, HsHostingAssetType> assignedToAssetReferenceValidation;
|
||||
private final HostingAssetEntityValidator.AlarmContact alarmContactValidation;
|
||||
|
||||
HostingAssetEntityValidator(
|
||||
@ -40,23 +40,23 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
this.bookingItemReferenceValidation = new ReferenceValidator<>(
|
||||
assetType.bookingItemPolicy(),
|
||||
assetType.bookingItemTypes(),
|
||||
HsHostingAssetEntity::getBookingItem,
|
||||
HsHostingAsset::getBookingItem,
|
||||
HsBookingItemEntity::getType);
|
||||
this.parentAssetReferenceValidation = new ReferenceValidator<>(
|
||||
assetType.parentAssetPolicy(),
|
||||
assetType.parentAssetTypes(),
|
||||
HsHostingAssetEntity::getParentAsset,
|
||||
HsHostingAssetEntity::getType);
|
||||
HsHostingAsset::getParentAsset,
|
||||
HsHostingAsset::getType);
|
||||
this.assignedToAssetReferenceValidation = new ReferenceValidator<>(
|
||||
assetType.assignedToAssetPolicy(),
|
||||
assetType.assignedToAssetTypes(),
|
||||
HsHostingAssetEntity::getAssignedToAsset,
|
||||
HsHostingAssetEntity::getType);
|
||||
HsHostingAsset::getAssignedToAsset,
|
||||
HsHostingAsset::getType);
|
||||
this.alarmContactValidation = alarmContactValidation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validateEntity(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateEntity(final HsHostingAsset assetEntity) {
|
||||
return sequentiallyValidate(
|
||||
() -> validateEntityReferencesAndProperties(assetEntity),
|
||||
() -> validateIdentifierPattern(assetEntity)
|
||||
@ -64,7 +64,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validateContext(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateContext(final HsHostingAsset assetEntity) {
|
||||
return sequentiallyValidate(
|
||||
() -> optionallyValidate(assetEntity.getBookingItem()),
|
||||
() -> optionallyValidate(assetEntity.getParentAsset()),
|
||||
@ -72,7 +72,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
);
|
||||
}
|
||||
|
||||
private List<String> validateEntityReferencesAndProperties(final HsHostingAssetEntity assetEntity) {
|
||||
private List<String> validateEntityReferencesAndProperties(final HsHostingAsset assetEntity) {
|
||||
return Stream.of(
|
||||
validateReferencedEntity(assetEntity, "bookingItem", bookingItemReferenceValidation::validate),
|
||||
validateReferencedEntity(assetEntity, "parentAsset", parentAssetReferenceValidation::validate),
|
||||
@ -86,17 +86,17 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
}
|
||||
|
||||
private List<String> validateReferencedEntity(
|
||||
final HsHostingAssetEntity assetEntity,
|
||||
final HsHostingAsset assetEntity,
|
||||
final String referenceFieldName,
|
||||
final BiFunction<HsHostingAssetEntity, String, List<String>> validator) {
|
||||
final BiFunction<HsHostingAsset, String, List<String>> validator) {
|
||||
return enrich(prefix(assetEntity.toShortString()), validator.apply(assetEntity, referenceFieldName));
|
||||
}
|
||||
|
||||
private List<String> validateProperties(final HsHostingAssetEntity assetEntity) {
|
||||
private List<String> validateProperties(final HsHostingAsset assetEntity) {
|
||||
return enrich(prefix(assetEntity.toShortString(), "config"), super.validateProperties(assetEntity));
|
||||
}
|
||||
|
||||
private static List<String> optionallyValidate(final HsHostingAssetEntity assetEntity) {
|
||||
private static List<String> optionallyValidate(final HsHostingAsset assetEntity) {
|
||||
return assetEntity != null
|
||||
? enrich(
|
||||
prefix(assetEntity.toShortString(), "parentAsset"),
|
||||
@ -112,7 +112,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
: emptyList();
|
||||
}
|
||||
|
||||
protected List<String> validateAgainstSubEntities(final HsHostingAssetEntity assetEntity) {
|
||||
protected List<String> validateAgainstSubEntities(final HsHostingAsset assetEntity) {
|
||||
return enrich(
|
||||
prefix(assetEntity.toShortString(), "config"),
|
||||
stream(propertyValidators)
|
||||
@ -124,7 +124,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
|
||||
// TODO.test: check, if there are any hosting assets which need this validation at all
|
||||
private String validateMaxTotalValue(
|
||||
final HsHostingAssetEntity hostingAsset,
|
||||
final HsHostingAsset hostingAsset,
|
||||
final ValidatableProperty<?, ?> propDef) {
|
||||
final var propName = propDef.propertyName();
|
||||
final var propUnit = ofNullable(propDef.unit()).map(u -> " " + u).orElse("");
|
||||
@ -140,7 +140,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
: null;
|
||||
}
|
||||
|
||||
private List<String> validateIdentifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
private List<String> validateIdentifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var expectedIdentifierPattern = identifierPattern(assetEntity);
|
||||
if (assetEntity.getIdentifier() == null ||
|
||||
!expectedIdentifierPattern.matcher(assetEntity.getIdentifier()).matches()) {
|
||||
@ -151,19 +151,19 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
protected abstract Pattern identifierPattern(HsHostingAssetEntity assetEntity);
|
||||
protected abstract Pattern identifierPattern(HsHostingAsset assetEntity);
|
||||
|
||||
static class ReferenceValidator<S, T> {
|
||||
|
||||
private final HsHostingAssetType.RelationPolicy policy;
|
||||
private final Set<T> referencedEntityTypes;
|
||||
private final Function<HsHostingAssetEntity, S> referencedEntityGetter;
|
||||
private final Function<HsHostingAsset, S> referencedEntityGetter;
|
||||
private final Function<S, T> referencedEntityTypeGetter;
|
||||
|
||||
public ReferenceValidator(
|
||||
final HsHostingAssetType.RelationPolicy policy,
|
||||
final Set<T> referencedEntityTypes,
|
||||
final Function<HsHostingAssetEntity, S> referencedEntityGetter,
|
||||
final Function<HsHostingAsset, S> referencedEntityGetter,
|
||||
final Function<S, T> referencedEntityTypeGetter) {
|
||||
this.policy = policy;
|
||||
this.referencedEntityTypes = referencedEntityTypes;
|
||||
@ -173,14 +173,14 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
|
||||
public ReferenceValidator(
|
||||
final HsHostingAssetType.RelationPolicy policy,
|
||||
final Function<HsHostingAssetEntity, S> referencedEntityGetter) {
|
||||
final Function<HsHostingAsset, S> referencedEntityGetter) {
|
||||
this.policy = policy;
|
||||
this.referencedEntityTypes = Set.of();
|
||||
this.referencedEntityGetter = referencedEntityGetter;
|
||||
this.referencedEntityTypeGetter = e -> null;
|
||||
}
|
||||
|
||||
List<String> validate(final HsHostingAssetEntity assetEntity, final String referenceFieldName) {
|
||||
List<String> validate(final HsHostingAsset assetEntity, final String referenceFieldName) {
|
||||
|
||||
final var actualEntity = referencedEntityGetter.apply(assetEntity);
|
||||
final var actualEntityType = actualEntity != null ? referencedEntityTypeGetter.apply(actualEntity) : null;
|
||||
@ -216,7 +216,7 @@ public abstract class HostingAssetEntityValidator extends HsEntityValidator<HsHo
|
||||
static class AlarmContact extends ReferenceValidator<HsOfficeContactEntity, Enum<?>> {
|
||||
|
||||
AlarmContact(final HsHostingAssetType.RelationPolicy policy) {
|
||||
super(policy, HsHostingAssetEntity::getAlarmContact);
|
||||
super(policy, HsHostingAsset::getAlarmContact);
|
||||
}
|
||||
|
||||
// hostmaster alert address is implicitly added where neccessary
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
import net.hostsharing.hsadminng.hs.hosting.generated.api.v1.model.HsHostingAssetResource;
|
||||
import net.hostsharing.hsadminng.hs.validation.HsEntityValidator;
|
||||
@ -12,7 +13,7 @@ import static net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType.*;
|
||||
|
||||
public class HostingAssetEntityValidatorRegistry {
|
||||
|
||||
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAssetEntity>> validators = new HashMap<>();
|
||||
private static final Map<Enum<HsHostingAssetType>, HsEntityValidator<HsHostingAsset>> validators = new HashMap<>();
|
||||
static {
|
||||
// HOWTO: add (register) new HsHostingAssetType-specific validators
|
||||
register(CLOUD_SERVER, new HsCloudServerHostingAssetValidator());
|
||||
@ -36,14 +37,14 @@ public class HostingAssetEntityValidatorRegistry {
|
||||
register(IPV6_NUMBER, new HsIPv6NumberHostingAssetValidator());
|
||||
}
|
||||
|
||||
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAssetEntity> validator) {
|
||||
private static void register(final Enum<HsHostingAssetType> type, final HsEntityValidator<HsHostingAsset> 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) {
|
||||
public static HsEntityValidator<HsHostingAsset> forType(final Enum<HsHostingAssetType> type) {
|
||||
if ( validators.containsKey(type)) {
|
||||
return validators.get(type);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -16,7 +16,7 @@ class HsCloudServerHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^vm[0-9][0-9][0-9][0-9]$");
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.system.SystemProcess;
|
||||
|
||||
import java.util.List;
|
||||
@ -59,12 +59,12 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier() + IDENTIFIER_SUFFIX));
|
||||
@ -73,7 +73,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public List<String> validateContext(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateContext(final HsHostingAsset assetEntity) {
|
||||
final var result = super.validateContext(assetEntity);
|
||||
|
||||
// TODO.spec: define which checks should get raised to error level
|
||||
@ -87,7 +87,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
return result;
|
||||
}
|
||||
|
||||
String toZonefileString(final HsHostingAssetEntity assetEntity) {
|
||||
String toZonefileString(final HsHostingAsset assetEntity) {
|
||||
// TODO.spec: we need to expand the templates (auto-...) in the same way as in Saltstack
|
||||
return """
|
||||
$ORIGIN {domain}.
|
||||
@ -104,7 +104,7 @@ class HsDomainDnsSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
.replace("{userRRs}", getPropertyValues(assetEntity, "user-RR") );
|
||||
}
|
||||
|
||||
private String fqdn(final HsHostingAssetEntity assetEntity) {
|
||||
private String fqdn(final HsHostingAsset assetEntity) {
|
||||
return assetEntity.getIdentifier().substring(0, assetEntity.getIdentifier().length()-IDENTIFIER_SUFFIX.length());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -42,12 +42,12 @@ class HsDomainHttpSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier() + IDENTIFIER_SUFFIX));
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -20,12 +20,12 @@ class HsDomainMboxSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier() + IDENTIFIER_SUFFIX));
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
@ -22,7 +22,7 @@ class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validateEntity(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateEntity(final HsHostingAsset assetEntity) {
|
||||
// TODO.impl: for newly created entities, check the permission of setting up a domain
|
||||
//
|
||||
// reject, if the domain is any of these:
|
||||
@ -51,7 +51,7 @@ class HsDomainSetupHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return identifierPattern;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -20,12 +20,12 @@ class HsDomainSmtpSetupHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^" + Pattern.quote(assetEntity.getParentAsset().getIdentifier() + IDENTIFIER_SUFFIX) + "$");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
ofNullable(entity.getParentAsset()).ifPresent(pa -> entity.setIdentifier(pa.getIdentifier() + IDENTIFIER_SUFFIX));
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@ -11,7 +11,7 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
|
||||
|
||||
class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
|
||||
private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$"; // also accepts legacy pac-names
|
||||
private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$"; // also accepts legacy pac-names
|
||||
private static final String EMAIL_ADDRESS_LOCAL_PART_REGEX = "[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+"; // RFC 5322
|
||||
private static final String EMAIL_ADDRESS_DOMAIN_PART_REGEX = "[a-zA-Z0-9.-]+";
|
||||
private static final String EMAIL_ADDRESS_FULL_REGEX = "^" + EMAIL_ADDRESS_LOCAL_PART_REGEX + "@" + EMAIL_ADDRESS_DOMAIN_PART_REGEX + "$";
|
||||
@ -29,7 +29,7 @@ class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preprocessEntity(final HsHostingAssetEntity entity) {
|
||||
public void preprocessEntity(final HsHostingAsset entity) {
|
||||
super.preprocessEntity(entity);
|
||||
super.preprocessEntity(entity);
|
||||
if (entity.getIdentifier() == null) {
|
||||
@ -38,11 +38,11 @@ class HsEMailAddressHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^"+ Pattern.quote(combineIdentifier(assetEntity)) + "$");
|
||||
}
|
||||
|
||||
private static String combineIdentifier(final HsHostingAssetEntity emailAddressAssetEntity) {
|
||||
private static String combineIdentifier(final HsHostingAsset emailAddressAssetEntity) {
|
||||
return emailAddressAssetEntity.getDirectValue("local-part", String.class) +
|
||||
ofNullable(emailAddressAssetEntity.getDirectValue("sub-domain", String.class)).map(s -> "." + s).orElse("") +
|
||||
"@" +
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetType;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
@ -10,8 +10,11 @@ import static net.hostsharing.hsadminng.hs.validation.StringProperty.stringPrope
|
||||
|
||||
class HsEMailAliasHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
|
||||
private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9]+)?$"; // also accepts legacy pac-names
|
||||
private static final String UNIX_USER_REGEX = "^[a-z][a-z0-9]{2}[0-9]{2}(-[a-z0-9][a-z0-9\\._-]*)?$"; // also accepts legacy pac-names
|
||||
private static final String EMAIL_ADDRESS_REGEX = "^[a-zA-Z0-9_!#$%&'*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"; // RFC 5322
|
||||
private static final String INCLUDE_REGEX = "^:include:/.*$";
|
||||
private static final String PIPE_REGEX = "^\\|.*$";
|
||||
private static final String DEV_NULL_REGEX = "^/dev/null$";
|
||||
public static final int EMAIL_ADDRESS_MAX_LENGTH = 320; // according to RFC 5321 and RFC 5322
|
||||
|
||||
HsEMailAliasHostingAssetValidator() {
|
||||
@ -19,13 +22,13 @@ class HsEMailAliasHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
AlarmContact.isOptional(),
|
||||
|
||||
arrayOf(
|
||||
stringProperty("target").maxLength(EMAIL_ADDRESS_MAX_LENGTH).matchesRegEx(UNIX_USER_REGEX, EMAIL_ADDRESS_REGEX)
|
||||
stringProperty("target").maxLength(EMAIL_ADDRESS_MAX_LENGTH).matchesRegEx(UNIX_USER_REGEX, EMAIL_ADDRESS_REGEX, INCLUDE_REGEX, PIPE_REGEX, DEV_NULL_REGEX)
|
||||
).required().minLength(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var webspaceIdentifier = assetEntity.getParentAsset().getIdentifier();
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9]+$");
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"-[a-z0-9][a-z0-9\\._-]*$");
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -20,7 +20,7 @@ class HsIPv4NumberHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return IPV4_REGEX;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
@ -24,7 +24,7 @@ class HsIPv6NumberHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> validateEntity(final HsHostingAssetEntity assetEntity) {
|
||||
public List<String> validateEntity(final HsHostingAsset assetEntity) {
|
||||
final var violations = super.validateEntity(assetEntity);
|
||||
|
||||
if (!isValidIPv6Address(assetEntity.getIdentifier())) {
|
||||
@ -35,7 +35,7 @@ class HsIPv6NumberHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return IPV6_REGEX;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -54,7 +54,7 @@ class HsManagedServerHostingAssetValidator extends HostingAssetEntityValidator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
return Pattern.compile("^vm[0-9][0-9][0-9][0-9]$");
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -11,11 +11,11 @@ class HsManagedWebspaceHostingAssetValidator extends HostingAssetEntityValidator
|
||||
super(
|
||||
MANAGED_WEBSPACE,
|
||||
AlarmContact.isOptional(),
|
||||
NO_EXTRA_PROPERTIES);
|
||||
NO_EXTRA_PROPERTIES); // TODO.impl: groupid missing, should be equal to main user
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var prefixPattern =
|
||||
!assetEntity.isLoaded()
|
||||
? assetEntity.getRelatedProject().getDebitor().getDefaultPrefix()
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -18,7 +18,7 @@ class HsMariaDbDatabaseHostingAssetValidator extends HostingAssetEntityValidator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Pattern identifierPattern(final HsHostingAssetEntity assetEntity) {
|
||||
protected Pattern identifierPattern(final HsHostingAsset assetEntity) {
|
||||
final var webspaceIdentifier = assetEntity.getParentAsset().getParentAsset().getIdentifier();
|
||||
return Pattern.compile("^"+webspaceIdentifier+"$|^"+webspaceIdentifier+"_[a-z0-9_]+$");
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package net.hostsharing.hsadminng.hs.hosting.asset.validators;
|
||||
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAssetEntity;
|
||||
import net.hostsharing.hsadminng.hs.hosting.asset.HsHostingAsset;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||