Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public class ValidationMessages {
// Entity Template validation messages
public static final String TEMPLATE_ALREADY_EXISTS = "An Entity Template already exists with the same identifier";
public static final String TEMPLATE_IDENTIFIER_MANDATORY = "Entity Template identifier is mandatory and cannot be blank";
public static final String PROPERTY_DEFINITIONS_MANDATORY = "Entity Template property definitions are mandatory and cannot be empty";
public static final String TEMPLATE_IDENTIFIER_CANNOT_CHANGE = "Entity Template identifier cannot be changed. Current identifier: ";
public static final String TEMPLATE_NAME_ALREADY_EXISTS = "The entity template name %s already exists";
public static final String TEMPLATE_NAME_MANDATORY = "Entity template name is mandatory and cannot be blank";
public static final String TEMPLATE_NAME_MAX_SIZE = "Entity template name must not exceed 255 characters";
Expand All @@ -23,8 +23,38 @@ public class ValidationMessages {

// Relation Definition validation messages
public static final String RELATION_NAME_MANDATORY = "Relation name is mandatory and cannot be blank";
public static final String RELATION_TARGET_IDENTIFIER_MANDATORY = "Target entity identifier is mandatory and cannot be blank";
public static final String RELATION_TARGET_IDENTIFIER_MANDATORY = "Target template identifier is mandatory and cannot be blank";
public static final String RELATION_NAME_MANDATORY_SIMPLE = "Relation name is mandatory";
public static final String RELATION_TARGET_IDENTIFIER_MANDATORY_SIMPLE = "Relation target identifier is mandatory";
public static final String RELATION_TARGET_IDENTIFIERS_NOT_NULL = "Target entity identifiers cannot be null";

// Property Rules validation messages - templates and specific constraints
public static final String PROPERTY_RULES_RULE_NOT_ALLOWED_FOR_TYPE = "{rule} rule is not allowed for {type} property type";
public static final String PROPERTY_RULES_MIN_MAX_CONSTRAINT_VIOLATED = "min_{constraint} must be less than or equal to max_{constraint}";
public static final String PROPERTY_RULES_MIN_LENGTH_NON_NEGATIVE = "min_length must be greater than or equal to 0";
public static final String PROPERTY_RULES_MAX_LENGTH_POSITIVE = "max_length must be greater than 0";
public static final String PROPERTY_RULES_BOOLEAN_NOT_ALLOWED = "Boolean properties do not accept any rules";
public static final String PROPERTY_RULES_NUMERIC_RULE_NOT_ALLOWED = "Numeric rule {rule} is not allowed for STRING properties";

public static final String PROPERTY_RULES_MUTUALLY_EXCLUSIVE = "{rule1} and {rule2} are mutually exclusive for STRING properties";

// Helper method to construct rules incompatibility message
public static String rulesAreIncompatible(String rule1, String rule2) {
return PROPERTY_RULES_MUTUALLY_EXCLUSIVE
.replace("{rule1}", rule1)
.replace("{rule2}", rule2);
}

// Helper method to construct rule-not-allowed message
public static String ruleNotAllowed(String rule, String propertyType) {
return PROPERTY_RULES_RULE_NOT_ALLOWED_FOR_TYPE
.replace("{rule}", rule)
.replace("{type}", propertyType);
}

// Helper method to construct min/max constraint violation message
public static String minMaxConstraintViolated(String constraint) {
return PROPERTY_RULES_MIN_MAX_CONSTRAINT_VIOLATED
.replace("{constraint}", constraint);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.decathlon.idp_core.domain.exception;
package com.decathlon.idp_core.domain.exception.entity_template;

import static com.decathlon.idp_core.domain.constant.ValidationMessages.TEMPLATE_ALREADY_EXISTS;

import com.decathlon.idp_core.domain.model.entity_template.EntityTemplate;
import com.decathlon.idp_core.domain.service.EntityTemplateService;
import com.decathlon.idp_core.domain.service.entity_template.EntityTemplateService;

/// Exception thrown when attempting to create an [EntityTemplate] with an identifier that already exists.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.decathlon.idp_core.domain.exception.entity_template;

import com.decathlon.idp_core.domain.model.entity_template.EntityTemplate;
import com.decathlon.idp_core.domain.service.entity_template.EntityTemplateService;
import com.decathlon.idp_core.infrastructure.adapters.api.handler.ApiExceptionHandler;

import static com.decathlon.idp_core.domain.constant.ValidationMessages.TEMPLATE_IDENTIFIER_CANNOT_CHANGE;

/// Exception thrown when attempting to change an [EntityTemplate] identifier after creation.
///
/// **Why this exception exists:**
/// - Entity template identifiers are immutable once the template is created
/// - Prevents accidental or malicious modifications to template identity
/// - Maintains separation of concerns between domain rules and HTTP status codes
///
/// **Usage patterns:**
/// - Thrown from [EntityTemplateService] when identifier modification is attempted
/// - Caught by [ApiExceptionHandler] and mapped to HTTP 400 status
/// - Contains the identifier that was attempted to be changed for debugging
public class EntityTemplateIdentifierCannotChangeException extends RuntimeException {

public EntityTemplateIdentifierCannotChangeException(String identifier) {
super(TEMPLATE_IDENTIFIER_CANNOT_CHANGE + identifier);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.decathlon.idp_core.domain.exception;
package com.decathlon.idp_core.domain.exception.entity_template;

import static com.decathlon.idp_core.domain.constant.ValidationMessages.TEMPLATE_NAME_ALREADY_EXISTS;

import com.decathlon.idp_core.domain.model.entity_template.EntityTemplate;
import com.decathlon.idp_core.domain.service.EntityTemplateService;
import com.decathlon.idp_core.domain.service.entity_template.EntityTemplateService;

/// Exception thrown when attempting to create or update an [EntityTemplate] with a name that already exists.
///
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.decathlon.idp_core.domain.exception;
package com.decathlon.idp_core.domain.exception.entity_template;

import java.util.UUID;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.decathlon.idp_core.domain.exception.entity_template;

import com.decathlon.idp_core.domain.model.enums.PropertyType;

/// Domain exception for property rule validation violations.
///
/// **Business purpose:** Represents the business rule violation when property rules
/// conflict with their assigned property type. This ensures data integrity
/// by preventing invalid rule configurations before persistence.
///
/// **Usage patterns:**
/// - Property template creation with invalid rules
/// - Property template updates introducing rule conflicts
public class PropertyDefinitionRulesConflictException extends RuntimeException {

/// Constructs a new exception for rule type conflict.
///
/// @param propertyName the name of the property with invalid rules
/// @param propertyType the data type of the property
/// @param violationMessage detailed explanation of what rule is invalid
public PropertyDefinitionRulesConflictException(String propertyName, PropertyType propertyType, String violationMessage) {
super("Property '" + propertyName + "' of type " + propertyType +
": " + violationMessage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import org.springframework.stereotype.Service;

import com.decathlon.idp_core.domain.exception.EntityNotFoundException;
import com.decathlon.idp_core.domain.exception.EntityTemplateNotFoundException;
import com.decathlon.idp_core.domain.exception.entity_template.EntityTemplateNotFoundException;
import com.decathlon.idp_core.domain.model.entity.Entity;
import com.decathlon.idp_core.domain.model.entity.EntitySummary;
import com.decathlon.idp_core.domain.port.EntityRepositoryPort;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package com.decathlon.idp_core.domain.service;
package com.decathlon.idp_core.domain.service.entity_template;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Collectors;

Expand All @@ -12,9 +11,9 @@
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import com.decathlon.idp_core.domain.exception.EntityTemplateAlreadyExistsException;
import com.decathlon.idp_core.domain.exception.EntityTemplateNameAlreadyExistsException;
import com.decathlon.idp_core.domain.exception.EntityTemplateNotFoundException;
import com.decathlon.idp_core.domain.exception.entity_template.EntityTemplateNotFoundException;
import com.decathlon.idp_core.domain.exception.entity_template.EntityTemplateAlreadyExistsException;
import com.decathlon.idp_core.domain.exception.entity_template.EntityTemplateNameAlreadyExistsException;
import com.decathlon.idp_core.domain.model.entity_template.EntityTemplate;
import com.decathlon.idp_core.domain.model.entity_template.PropertyDefinition;
import com.decathlon.idp_core.domain.model.entity_template.PropertyRules;
Expand Down Expand Up @@ -43,6 +42,7 @@
public class EntityTemplateService {

private final EntityTemplateRepositoryPort entityTemplateRepositoryPort;
private final EntityTemplateValidationService entityTemplateValidationService;

/// Retrieves paginated entity templates for management interface display.
///
Expand Down Expand Up @@ -79,21 +79,15 @@ public EntityTemplate getEntityTemplateByIdentifier(String identifier) {
/// **Business rules enforced:**
/// - If `identifier` is provided it must not already exist in the system.
/// - If `name` is provided it must not already exist in the system.
/// - Validation of property rules according to their defined constraints.
///
/// @param entityTemplate validated template to create and persist
/// @return the persisted template with generated identifiers
/// @throws EntityTemplateAlreadyExistsException when identifier already exists
/// @throws EntityTemplateNameAlreadyExistsException when name already exists
@Transactional
public EntityTemplate createEntityTemplate(@Valid EntityTemplate entityTemplate) {
if (entityTemplate.identifier() != null &&
entityTemplateRepositoryPort.existsByIdentifier(entityTemplate.identifier())) {
throw new EntityTemplateAlreadyExistsException(entityTemplate.identifier());
}
if (entityTemplate.name() != null &&
entityTemplateRepositoryPort.existsByName(entityTemplate.name())) {
throw new EntityTemplateNameAlreadyExistsException(entityTemplate.name());
}
entityTemplateValidationService.validateForCreation(entityTemplate);
return entityTemplateRepositoryPort.save(entityTemplate);
}

Expand All @@ -112,41 +106,44 @@ public EntityTemplate createEntityTemplate(@Valid EntityTemplate entityTemplate)
/// - *Matched by name* → existing ID is preserved, other fields are overwritten.
/// - *Not matched* → treated as a new definition (no ID yet).
/// - *Missing from update* → removed (handled downstream by the persistence adapter).
/// - Validation of property rules according to their defined constraints.
///
/// @param identifier current business identifier of the template to update
/// @param updatedTemplate validated template carrying the desired state
/// @param entityTemplate validated template carrying the desired state
/// @return the persisted template after merge, with generated or preserved identifiers
/// @throws EntityTemplateNotFoundException when no template matches `identifier`
/// @throws EntityTemplateAlreadyExistsException when renaming would cause a duplicate
@Transactional
public EntityTemplate putEntityTemplate(String identifier, @Valid EntityTemplate updatedTemplate) {
public EntityTemplate updateEntityTemplate(String identifier, @Valid EntityTemplate entityTemplate) {
EntityTemplate existingTemplate = getEntityTemplateByIdentifier(identifier);

if (!identifier.equals(updatedTemplate.identifier()) &&
entityTemplateRepositoryPort.existsByIdentifier(updatedTemplate.identifier())) {
throw new EntityTemplateAlreadyExistsException(updatedTemplate.identifier());
}

if (updatedTemplate.name() != null &&
!Objects.equals(existingTemplate.name(), updatedTemplate.name()) &&
entityTemplateRepositoryPort.existsByName(updatedTemplate.name())) {
throw new EntityTemplateNameAlreadyExistsException(updatedTemplate.name());
}

EntityTemplate mergedTemplate = new EntityTemplate(
existingTemplate.id(),
updatedTemplate.identifier(),
updatedTemplate.name(),
updatedTemplate.description(),
entityTemplate.identifier(),
entityTemplate.name(),
entityTemplate.description(),
mergePropertyDefinitions(existingTemplate.propertiesDefinitions(),
updatedTemplate.propertiesDefinitions()),
entityTemplate.propertiesDefinitions()),
mergeRelationDefinitions(existingTemplate.relationsDefinitions(),
updatedTemplate.relationsDefinitions())
entityTemplate.relationsDefinitions())
);

entityTemplateValidationService.validateForUpdate(identifier, existingTemplate.name(), mergedTemplate);
return entityTemplateRepositoryPort.save(mergedTemplate);
}

/// Deletes an entity template by business identifier with existence validation.
///
/// **Contract:** Validates template existence before deletion to ensure referential
/// integrity. Deletion cascades through persistence layer according to configured
/// relationships. This operation is irreversible once committed.
///
/// @param identifier unique business identifier of template to delete
/// @throws EntityTemplateNotFoundException when template doesn't exist
@Transactional
public void deleteEntityTemplate(String identifier) {
entityTemplateValidationService.validateForDeletion(identifier);
entityTemplateRepositoryPort.deleteByIdentifier(identifier);
}

private List<PropertyDefinition> mergePropertyDefinitions(
List<PropertyDefinition> existing,
List<PropertyDefinition> updated) {
Expand All @@ -162,16 +159,14 @@ private List<PropertyDefinition> mergePropertyDefinitions(
for (PropertyDefinition prop : updated) {
PropertyDefinition existingProp = existingMap.get(prop.name());
if (existingProp != null) {
// Records are immutable - create a new instance
PropertyDefinition merged = new PropertyDefinition(
result.add(new PropertyDefinition(
existingProp.id(),
prop.name(),
prop.description(),
prop.type(),
prop.required(),
mergePropertyRules(existingProp.rules(), prop.rules())
);
result.add(merged);
));
} else {
result.add(prop);
}
Expand All @@ -188,7 +183,6 @@ private PropertyRules mergePropertyRules(PropertyRules existingRules, PropertyRu
return newRules;
}

// Records are immutable - create a new instance
return new PropertyRules(
existingRules.id(),
newRules.format(),
Expand Down Expand Up @@ -216,15 +210,13 @@ private List<RelationDefinition> mergeRelationDefinitions(
for (RelationDefinition rel : updated) {
RelationDefinition existingRel = existingMap.get(rel.name());
if (existingRel != null) {
// Records are immutable - create a new instance
RelationDefinition merged = new RelationDefinition(
result.add(new RelationDefinition(
existingRel.id(),
rel.name(),
rel.targetTemplateIdentifier(),
rel.required(),
rel.toMany()
);
result.add(merged);
));
} else {
result.add(rel);
}
Expand All @@ -233,24 +225,4 @@ private List<RelationDefinition> mergeRelationDefinitions(
return result;
}


/// Deletes an entity template by business identifier with existence validation.
///
/// **Contract:** Validates template existence before deletion to ensure referential
/// integrity. Deletion cascades through persistence layer according to configured
/// relationships. This operation is irreversible once committed.
///
/// @param identifier unique business identifier of template to delete
/// @throws EntityTemplateNotFoundException when template doesn't exist
@Transactional
public void deleteEntityTemplate(String identifier) {
if (identifier == null) {
throw new IllegalArgumentException("Template identifier must not be null");
}
if (!entityTemplateRepositoryPort.existsByIdentifier(identifier)) {
throw new EntityTemplateNotFoundException("identifier", identifier);
}
entityTemplateRepositoryPort.deleteByIdentifier(identifier);
}

}
Loading
Loading