diff --git a/judo-runtime-core-dao-core/src/main/java/hu/blackbelt/judo/runtime/core/dao/core/processors/InsertPayloadDaoProcessor.java b/judo-runtime-core-dao-core/src/main/java/hu/blackbelt/judo/runtime/core/dao/core/processors/InsertPayloadDaoProcessor.java index c00d87a7..89045837 100644 --- a/judo-runtime-core-dao-core/src/main/java/hu/blackbelt/judo/runtime/core/dao/core/processors/InsertPayloadDaoProcessor.java +++ b/judo-runtime-core-dao-core/src/main/java/hu/blackbelt/judo/runtime/core/dao/core/processors/InsertPayloadDaoProcessor.java @@ -36,7 +36,7 @@ import org.eclipse.emf.ecore.resource.ResourceSet; import java.util.*; -import java.util.function.Function; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkArgument; @@ -47,8 +47,8 @@ /** - * Analyzing the inserted entities recursively and generating the required executable statements. - * The entities have be transfer objects which are mapped to other entities via aliases. + * Analyze the inserted entities recursively and generate the required executable statements. + * The entities must be transfer objects mapped to other entities through aliases. * * Rules: * - The root type cannot have ID - because its is update @@ -60,18 +60,18 @@ public class InsertPayloadDaoProcessor extends PayloadDaoProcessor { private final AddReferencePayloadDaoProcessor addReferenceProcessor; - private final Function defaultValuesProvider; + private final BiConsumer defaultValuesApplier; Metadata metadata; public InsertPayloadDaoProcessor(ResourceSet resourceSet, IdentifierProvider identifierProvider, QueryFactory queryFactory, InstanceCollector instanceCollector, - Function defaultValuesProvider, + BiConsumer defaultValuesApplier, Metadata metadata) { super(resourceSet, identifierProvider, queryFactory, instanceCollector); addReferenceProcessor = new AddReferencePayloadDaoProcessor(resourceSet, identifierProvider, queryFactory, instanceCollector); - this.defaultValuesProvider = defaultValuesProvider; + this.defaultValuesApplier = defaultValuesApplier; this.metadata = metadata; } @@ -94,11 +94,7 @@ Collection> collectStatements(EClass mappedTransferObjectType, checkArgument(getAsmUtils().isMappedTransferObjectType(mappedTransferObjectType), "Type have to be mapped transfer object"); - // Set default values of transfer object type (that are missing from payload) - Payload defaults = defaultValuesProvider.apply(mappedTransferObjectType); - payload.putAll(defaults.entrySet().stream() - .filter(e -> !payload.containsKey(e.getKey()) && e.getValue() != null) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()))); + defaultValuesApplier.accept(mappedTransferObjectType, payload); InsertStatement.InsertStatementBuilder currentStatementBuilder = InsertStatement.buildInsertStatement() @@ -123,9 +119,6 @@ Collection> collectStatements(EClass mappedTransferObjectType, .map(defaultTransferObjectTypeName -> getAsmUtils().resolve(defaultTransferObjectTypeName).orElse(null)) .filter(t -> t instanceof EClass).map(t -> (EClass) t); - // Get default values of entity type - final Payload entityDefaults = defaultTransferObjectType.map(t -> defaultValuesProvider.apply(t)).orElse(Payload.empty()); - // Processing attributes attributes = mappedTransferObjectType.getEAllAttributes().stream() .filter( @@ -154,7 +147,6 @@ Collection> collectStatements(EClass mappedTransferObjectType, checkForbiddenReferenceUpdates(references, payload); - // Add attributes (mapped name of attribute resolved here) attributes.stream() .collect(Collectors.toMap( @@ -184,21 +176,27 @@ && getAsmUtils().getMappedReference(r).orElseThrow().isContainment() ) ); - - // Add entity default attributes that are not mapped to transfer object type - defaultTransferObjectType.ifPresent(t -> t.getEAllAttributes().stream() - .filter(a -> entityDefaults.get(a.getName()) != null) - .collect(Collectors.toMap( - identity(), - a -> getAsmUtils().getMappedAttribute(a).orElse(a))) - .entrySet().stream() - .filter(e -> !mappedTransferObjectType.getEAllAttributes().stream().anyMatch(ta -> AsmUtils.equals(e.getValue(), getAsmUtils().getMappedAttribute(ta).orElse(null)))) - .forEach( - a -> currentStatement.getInstance().addAttributeValue( - a.getValue(), - getTransferObjectValueAsEntityValueFromPayload(entityDefaults, a.getKey(), a.getValue())) - ) - ); + // Get default values of entity type + final Payload entityDefaults = Payload.empty(); + + boolean isDTOPresentAndDifferentThanMappedTO = + defaultTransferObjectType.isPresent() && !AsmUtils.equals(defaultTransferObjectType.get(), mappedTransferObjectType); + if (isDTOPresentAndDifferentThanMappedTO) { + defaultValuesApplier.accept(defaultTransferObjectType.get(), entityDefaults); + + // Add entity default attributes that are not mapped to transfer object type + Map dtoAttributeDefaults = + defaultTransferObjectType.get().getEAllAttributes().stream() + .filter(a -> entityDefaults.get(a.getName()) != null) + .collect(Collectors.toMap(identity(), a -> getAsmUtils().getMappedAttribute(a).orElse(a))); + for (Map.Entry e : dtoAttributeDefaults.entrySet()) { + EAttribute dtoAttribute = e.getKey(); + EAttribute mappedAttribute = e.getValue(); + if (mappedTransferObjectType.getEAllAttributes().stream().noneMatch(ta -> AsmUtils.equals(mappedAttribute, getAsmUtils().getMappedAttribute(ta).orElse(null)))) { + currentStatement.getInstance().addAttributeValue(mappedAttribute, getTransferObjectValueAsEntityValueFromPayload(entityDefaults, dtoAttribute, mappedAttribute)); + } + } + } // Inserting all embedded reference references.stream() @@ -257,26 +255,28 @@ && getAsmUtils().getMappedReference(r).orElseThrow().isContainment() ); // Add entity default references that are not mapped to transfer object type - defaultTransferObjectType.ifPresent(t -> t.getEAllReferences().stream() - .filter(r -> entityDefaults.get(r.getName()) != null) - .collect(Collectors.toMap( - identity(), - r -> getAsmUtils().getMappedReference(r).orElse(r))) - .entrySet().stream() - .filter(e -> !mappedTransferObjectType.getEAllReferences().stream().anyMatch(tr -> AsmUtils.equals(e.getValue(), getAsmUtils().getMappedReference(tr).orElse(null)))) - .forEach( - r -> currentStatements.addAll( - addReferenceProcessor.addReference( - r.getValue(), - r.getKey().isMany() - ? entityDefaults.getAsCollectionPayload(r.getKey().getName()).stream().map(p -> p.getAs(getIdentifierProvider().getType(), getIdentifierProvider().getName())).collect(Collectors.toSet()) - : Collections.singleton(entityDefaults.getAsPayload(r.getKey().getName()).getAs(getIdentifierProvider().getType(), getIdentifierProvider().getName())), - currentStatement.getInstance().getIdentifier(), - true - ) - ) - ) - ); + if (isDTOPresentAndDifferentThanMappedTO) { + Map dtoReferences = + defaultTransferObjectType.get().getEAllReferences().stream() + .filter(r -> entityDefaults.get(r.getName()) != null) + .collect(Collectors.toMap(identity(), r -> getAsmUtils().getMappedReference(r).orElse(r))); + for (Map.Entry e : dtoReferences.entrySet()) { + EReference dtoReference = e.getKey(); + EReference mappedReference = e.getValue(); + if (mappedTransferObjectType.getEAllReferences().stream().noneMatch(tr -> AsmUtils.equals(mappedReference, getAsmUtils().getMappedReference(tr).orElse(null)))) { + Set ids; + if (dtoReference.isMany()) { + ids = entityDefaults.getAsCollectionPayload(dtoReference.getName()).stream() + .map(p -> p.getAs(getIdentifierProvider().getType(), getIdentifierProvider().getName())) + .collect(Collectors.toSet()); + } else { + ids = Collections.singleton(entityDefaults.getAsPayload(dtoReference.getName()) + .getAs(getIdentifierProvider().getType(), getIdentifierProvider().getName())); + } + currentStatements.addAll(addReferenceProcessor.addReference(mappedReference, ids, currentStatement.getInstance().getIdentifier(), true)); + } + } + } statements.addAll(currentStatements); return ImmutableSet.copyOf(currentStatements); diff --git a/judo-runtime-core-dao-core/src/main/java/hu/blackbelt/judo/runtime/core/dao/core/processors/UpdatePayloadDaoProcessor.java b/judo-runtime-core-dao-core/src/main/java/hu/blackbelt/judo/runtime/core/dao/core/processors/UpdatePayloadDaoProcessor.java index d92ab372..2ecd0564 100644 --- a/judo-runtime-core-dao-core/src/main/java/hu/blackbelt/judo/runtime/core/dao/core/processors/UpdatePayloadDaoProcessor.java +++ b/judo-runtime-core-dao-core/src/main/java/hu/blackbelt/judo/runtime/core/dao/core/processors/UpdatePayloadDaoProcessor.java @@ -39,6 +39,7 @@ import org.eclipse.emf.ecore.resource.ResourceSet; import java.util.*; +import java.util.function.BiConsumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -67,14 +68,14 @@ public class UpdatePayloadDaoProcessor extends PayloadDaoProcessor { public UpdatePayloadDaoProcessor(ResourceSet resourceSet, IdentifierProvider identifierProvider, QueryFactory queryFactory, InstanceCollector instanceCollector, - Function defaultValuesProvider, + BiConsumer defaultValuesApplier, Metadata metadata, boolean optimisticLockEnabled) { super(resourceSet, identifierProvider, queryFactory, instanceCollector); this.metadata = metadata; this.optimisticLockEnabled = optimisticLockEnabled; insertPayloadDaoProcessor = new InsertPayloadDaoProcessor(resourceSet, identifierProvider, - queryFactory, instanceCollector, defaultValuesProvider, metadata); + queryFactory, instanceCollector, defaultValuesApplier, metadata); deletePayloadDaoProcessor = new DeletePayloadDaoProcessor(resourceSet, identifierProvider, queryFactory, instanceCollector); addReferencePayloadDaoProcessor = new AddReferencePayloadDaoProcessor(resourceSet, identifierProvider, diff --git a/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/AbstractRdbmsDAO.java b/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/AbstractRdbmsDAO.java index 93a6776f..e3e9bafb 100644 --- a/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/AbstractRdbmsDAO.java +++ b/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/AbstractRdbmsDAO.java @@ -82,6 +82,14 @@ public Payload getDefaultsOf(EClass clazz) { } } + @Override + public void applyDefaultsOf(EClass clazz, Payload payload) { + try (MetricsCancelToken ct = getMetricsCollector().start(METRICS_DAO_QUERY)) { + applyDeepDefaultsOf(clazz, payload); + logResult(payload); + } + } + @Override public Collection getRangeOf(EReference reference, Payload payload, QueryCustomizer queryCustomizer, boolean stateful) { try (MetricsCancelToken ct = getMetricsCollector().start(METRICS_DAO_QUERY)) { @@ -628,6 +636,8 @@ private boolean addStaticFeaturesToPayload(final Payload payload, final EClass c protected abstract Payload readDefaultsOf(EClass clazz); + protected abstract void applyDeepDefaultsOf(EClass clazz, Payload payload); + protected abstract Collection readRangeOf(EReference reference, Payload payload, QueryCustomizer queryCustomizer, boolean stateful); protected abstract long calculateNumberRangeOf(EReference reference, Payload payload, QueryCustomizer queryCustomizer, boolean stateful); diff --git a/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/RdbmsDAOImpl.java b/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/RdbmsDAOImpl.java index bad6b242..165db417 100644 --- a/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/RdbmsDAOImpl.java +++ b/judo-runtime-core-dao-rdbms/src/main/java/hu/blackbelt/judo/runtime/core/dao/rdbms/RdbmsDAOImpl.java @@ -20,6 +20,27 @@ * #L% */ +import java.security.Principal; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.sql.DataSource; + import com.google.common.collect.ImmutableSet; import hu.blackbelt.judo.dao.api.DAO; import hu.blackbelt.judo.dao.api.IdentifierProvider; @@ -30,7 +51,12 @@ import hu.blackbelt.judo.meta.asm.runtime.AsmUtils; import hu.blackbelt.judo.runtime.core.MetricsCollector; import hu.blackbelt.judo.runtime.core.dao.core.collectors.InstanceCollector; -import hu.blackbelt.judo.runtime.core.dao.core.processors.*; +import hu.blackbelt.judo.runtime.core.dao.core.processors.AddReferencePayloadDaoProcessor; +import hu.blackbelt.judo.runtime.core.dao.core.processors.DeletePayloadDaoProcessor; +import hu.blackbelt.judo.runtime.core.dao.core.processors.InsertPayloadDaoProcessor; +import hu.blackbelt.judo.runtime.core.dao.core.processors.PayloadDaoProcessor; +import hu.blackbelt.judo.runtime.core.dao.core.processors.RemoveReferencePayloadDaoProcessor; +import hu.blackbelt.judo.runtime.core.dao.core.processors.UpdatePayloadDaoProcessor; import hu.blackbelt.judo.runtime.core.dao.core.statements.InsertStatement; import hu.blackbelt.judo.runtime.core.dao.core.statements.Statement; import hu.blackbelt.judo.runtime.core.dao.core.values.Metadata; @@ -38,27 +64,17 @@ import hu.blackbelt.judo.runtime.core.dao.rdbms.executors.SelectStatementExecutor; import hu.blackbelt.judo.runtime.core.dao.rdbms.executors.StatementExecutor; import hu.blackbelt.judo.runtime.core.query.QueryFactory; -import lombok.*; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; import lombok.extern.slf4j.Slf4j; -import org.eclipse.emf.common.util.*; +import org.eclipse.emf.common.util.UniqueEList; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import javax.sql.DataSource; -import java.security.Principal; -import java.sql.SQLException; -import java.time.LocalDateTime; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static hu.blackbelt.judo.meta.asm.runtime.AsmUtils.getReferenceFQName; @@ -78,6 +94,7 @@ public class RdbmsDAOImpl extends AbstractRdbmsDAO implements DAO { private static final String STATEFUL = "STATEFUL"; private static final String ROLLBACK = "ROLLBACK"; public static final String CREATED = "__$created"; + public static final String DEFAULT_VALUES_LOADED_KEY = "__defaultValuesLoaded"; @Getter private final AsmModel asmModel; private final DataSource dataSource; @@ -121,8 +138,12 @@ private RdbmsDAOImpl( this.modifyStatementExecutor = modifyStatementExecutor; } - private Function getDefaultValuesProvider() { - return (clazz) -> hasDefaults(clazz) ? getDefaultsOf(clazz) : Payload.empty(); + private BiConsumer getDefaultValuesApplier() { + return (clazz, payload) -> { + if (hasDefaults(clazz)) { + applyDefaultsOf(clazz, payload); + } + }; } private boolean hasDefaults(EClass clazz) { @@ -152,7 +173,7 @@ private boolean haveDefaultsAnyOf(final Collection classes, final Collec .filter(t -> t instanceof EClass).map(t -> (EClass) t)) .filter(eClass -> eClass.isPresent()) .flatMap(eClass -> eClass.get().getEAllStructuralFeatures().stream()) - .anyMatch(c -> AsmUtils.getExtensionAnnotationByName(c, "default", false).isPresent())) {; + .anyMatch(c -> AsmUtils.getExtensionAnnotationByName(c, "default", false).isPresent())) { return true; } checked.addAll(classes); @@ -165,11 +186,11 @@ private boolean haveDefaultsAnyOf(final Collection classes, final Collec protected InsertPayloadDaoProcessor getInsertPayloadProcessor(Metadata metadata) { return new InsertPayloadDaoProcessor(asmModel.getResourceSet(), - getIdentifierProvider(), - queryFactory, - instanceCollector, - getDefaultValuesProvider(), - metadata); + getIdentifierProvider(), + queryFactory, + instanceCollector, + getDefaultValuesApplier(), + metadata); } protected DeletePayloadDaoProcessor getDeletePayloadProcessor() { @@ -181,12 +202,12 @@ protected DeletePayloadDaoProcessor getDeletePayloadProcessor() { protected UpdatePayloadDaoProcessor getUpdatePayloadProcessor(Metadata metadata) { return new UpdatePayloadDaoProcessor(asmModel.getResourceSet(), - getIdentifierProvider(), - queryFactory, - instanceCollector, - getDefaultValuesProvider(), - metadata, - optimisticLockEnabled); + getIdentifierProvider(), + queryFactory, + instanceCollector, + getDefaultValuesApplier(), + metadata, + optimisticLockEnabled); } protected AddReferencePayloadDaoProcessor getAddReferencePayloadProcessor() { @@ -653,85 +674,96 @@ protected Optional readMetadataByIdentifier(EClass clazz, ID identifier @Override protected Payload readDefaultsOf(EClass clazz) { - final Payload template = Payload.empty(); + Payload template = Payload.empty(); AsmUtils asmUtils = new AsmUtils(asmModel.getResourceSet()); - clazz.getEAllAttributes().stream() - .filter(EStructuralFeature::isChangeable) - .forEach(a -> AsmUtils.getExtensionAnnotationValue(a, "default", false).ifPresent(defaultFeatureName -> { - final EAttribute defaultAttribute = clazz.getEAllAttributes().stream() - .filter(df -> Objects.equals(df.getName(), defaultFeatureName)) - .findAny() - .orElse(null); - - if (defaultAttribute != null) { - final Payload defaultValue = getStaticData(defaultAttribute); - if (defaultValue.get(defaultAttribute.getName()) == null && a.isRequired()) { - throw new IllegalStateException("Default attribute value is undefined on required attribute: " + defaultFeatureName); - } - template.put(a.getName(), defaultValue.get(defaultAttribute.getName())); - } - })); - - clazz.getEAllReferences().stream() - .filter(EStructuralFeature::isChangeable) - .forEach(r -> AsmUtils.getExtensionAnnotationValue(r, "default", false).ifPresent(defaultReferenceName -> { - final EReference defaultReference = clazz.getEAllReferences().stream() - .filter(df -> Objects.equals(df.getName(), defaultReferenceName)) - .findAny() - .orElseThrow(() -> new IllegalStateException("Default reference not found: " + defaultReferenceName)); - - final List defaultValues = getAllReferencedInstancesOf(defaultReference, defaultReference.getEReferenceType()); - if (defaultReference.isMany()) { - template.put(r.getName(), defaultValues); - } else { - final Payload defaultValue = !defaultValues.isEmpty() ? defaultValues.get(0) : null; - if (r.getLowerBound() > 0 && defaultValue == null) { - throw new IllegalStateException("Default reference value is undefined on required reference: " + defaultReferenceName); - } - template.put(r.getName(), defaultValue); - } - })); + List attributes = clazz.getEAllAttributes().stream().filter(a -> a.isChangeable() && !a.isDerived()).toList(); + for (EAttribute attribute : attributes) { + String defaultAttributeName = AsmUtils.getExtensionAnnotationValue(attribute, "default", false).orElse(null); + if (defaultAttributeName != null) { + Optional defaultAttribute = + clazz.getEAllAttributes().stream() + .filter(a -> Objects.equals(a.getName(), defaultAttributeName)) + .findAny(); + defaultAttribute.ifPresent(eAttribute -> template.put(attribute.getName(), getStaticData(eAttribute).get(eAttribute.getName()))); + } + } - final Optional mappedEntityType = asmUtils.getMappedEntityType(clazz); + // in case a composition has default value, it might cause problems + List references = clazz.getEAllReferences().stream().filter(r -> r.isChangeable() && !r.isDerived()).toList(); + for (EReference reference : references) { + String defaultReferenceName = AsmUtils.getExtensionAnnotationValue(reference, "default", false).orElse(null); + if (defaultReferenceName != null) { + EReference defaultReference = + clazz.getEAllReferences().stream() + .filter(df -> Objects.equals(df.getName(), defaultReferenceName)) + .findAny() + .orElseThrow(() -> new IllegalStateException("Default reference not found for %s: %s".formatted(AsmUtils.getReferenceFQName(reference), defaultReferenceName))); + + List defaultValues = getAllReferencedInstancesOf(defaultReference, defaultReference.getEReferenceType()); + if (defaultReference.isMany()) { + template.put(reference.getName(), defaultValues); + } else { + template.put(reference.getName(), !defaultValues.isEmpty() ? defaultValues.get(0) : null); + } + } + } - final Optional defaultTransferObjectType = mappedEntityType - .flatMap(e -> AsmUtils.getExtensionAnnotationValue(e, "defaultRepresentation", false) - .flatMap(dr -> asmUtils.resolve(dr))) - .filter(t -> t instanceof EClass).map(t -> (EClass) t); + Optional defaultTransferObjectType = + asmUtils.getMappedEntityType(clazz) + .flatMap(e -> AsmUtils.getExtensionAnnotationValue(e, "defaultRepresentation", false) + .flatMap(asmUtils::resolve)) + .filter(t -> t instanceof EClass) + .map(t -> (EClass) t); if (defaultTransferObjectType.isPresent() && !Objects.equals(defaultTransferObjectType.get(), clazz)) { - final Payload entityTypeDefaults = readDefaultsOf(defaultTransferObjectType.get()); + // if the transfer object has a mapping, read default values of the mapped features + // and add them to the template if they are not already present + Payload entityTypeDefaults = readDefaultsOf(defaultTransferObjectType.get()); template.putAll(clazz.getEAllAttributes().stream() - .filter(a -> !template.containsKey(a.getName()) && asmUtils.getMappedAttribute(a).isPresent()) - .collect(Collectors.toMap( - identity(), - a -> asmUtils.getMappedAttribute(a).orElseThrow(() -> new IllegalStateException("Mapped attribute not found: " + AsmUtils.getAttributeFQName(a))))) - .entrySet().stream() - .filter(e -> entityTypeDefaults.get(e.getValue().getName()) != null && !AsmUtils.annotatedAsTrue(e.getValue(), "unmappedDefaultOnly")) - .collect(Collectors.toMap( - e -> e.getKey().getName(), - e -> entityTypeDefaults.get(e.getValue().getName())))); + .filter(a -> !template.containsKey(a.getName()) && asmUtils.getMappedAttribute(a).isPresent()) + .collect(Collectors.toMap(identity(), a -> asmUtils.getMappedAttribute(a).get())).entrySet().stream() + .filter(e -> entityTypeDefaults.get(e.getValue().getName()) != null && !AsmUtils.annotatedAsTrue(e.getValue(), "unmappedDefaultOnly")) + .collect(Collectors.toMap(e -> e.getKey().getName(), e -> entityTypeDefaults.get(e.getValue().getName())))); template.putAll(clazz.getEAllReferences().stream() - .filter(r -> template.get(r.getName()) == null && asmUtils.getMappedReference(r).isPresent()) - .collect(Collectors.toMap( - identity(), - r -> asmUtils.getMappedReference(r).orElseThrow(() -> new IllegalStateException("Mapped reference not found: " + AsmUtils.getReferenceFQName(r))))) - .entrySet().stream() - .filter(e -> entityTypeDefaults.get(e.getValue().getName()) != null && !AsmUtils.annotatedAsTrue(e.getValue(), "unmappedDefaultOnly")) - .collect(Collectors.toMap( - e -> e.getKey().getName(), - e -> entityTypeDefaults.get(e.getValue().getName())))); + .filter(r -> !template.containsKey(r.getName()) && asmUtils.getMappedReference(r).isPresent()) + .collect(Collectors.toMap(identity(), r -> asmUtils.getMappedReference(r).get())).entrySet().stream() + .filter(e -> entityTypeDefaults.get(e.getValue().getName()) != null && !AsmUtils.annotatedAsTrue(e.getValue(), "unmappedDefaultOnly")) + .collect(Collectors.toMap(e -> e.getKey().getName(), e -> entityTypeDefaults.get(e.getValue().getName())))); } return template; } + @Override + protected void applyDeepDefaultsOf(EClass clazz, Payload payload) { + AsmUtils asmUtils = new AsmUtils(asmModel.getResourceSet()); + hu.blackbelt.judo.runtime.core.PayloadTraverser.builder() + .processor((_payload, context) -> { + // checking for identifier might be to strict here + // TODO: if this causes issues later, it should be a parameter + if (!requireNonNullElse(_payload.getAs(Boolean.class, DEFAULT_VALUES_LOADED_KEY), false) + && !_payload.containsKey(identifierProvider.getName())) { + Payload defaultValues = readDefaultsOf(context.getType()); + for (Map.Entry e : defaultValues.entrySet()) { + // putIfAbsent is intentionally avoided to keep explicitly set null values + if (!_payload.containsKey(e.getKey())) { + _payload.put(e.getKey(), e.getValue()); + } + } + _payload.put(DEFAULT_VALUES_LOADED_KEY, true); + } + }) + .predicate(reference -> asmUtils.getMappedReference(reference).map(r -> r.isChangeable() && !r.isDerived()).orElse(false)) + .build() + .traverse(payload, clazz); + } + @Override protected Collection readRangeOf(final EReference reference, final Payload payload, QueryCustomizer queryCustomizer, boolean stateful) { final EReference rangeTransferRelation = AsmUtils.getExtensionAnnotationValue(reference, "range", false) .map(rangeTransferRelationName -> reference.getEContainingClass().getEAllReferences().stream().filter(r -> rangeTransferRelationName.equals(r.getName())).findAny() - .orElseThrow(() -> new IllegalStateException("Refence not found on containing class: " + rangeTransferRelationName))) + .orElseThrow(() -> new IllegalStateException("Reference not found on containing class: " + rangeTransferRelationName))) .orElseThrow(() -> new IllegalStateException("No range defined")); ID instanceId = payload != null ? payload.getAs(identifierProvider.getType(), identifierProvider.getName()) : null; diff --git a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultDispatcher.java b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultDispatcher.java index 31e99394..a0583c57 100644 --- a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultDispatcher.java +++ b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/DefaultDispatcher.java @@ -446,6 +446,7 @@ void processParameter(EOperation operation, EParameter parameter, EClassifier pa final RequestConverter requestConverter = RequestConverter.builder() .transferObjectType(transferObjectType) .coercer(dataTypeManager.getCoercer()) + .dao(dao) .asmModel(asmModel) .validatorProvider(validatorProvider) .trimString(trimString) @@ -828,12 +829,12 @@ private void validateAndConvertParameters(Map exchange, EOperati .filter(parameter -> (parameter.getEType() instanceof EClass)) .collect(Collectors.toList()); - parameters.forEach(parameter -> { + for (EParameter parameter : parameters) { final EClass transferObjectType = (EClass) parameter.getEType(); final Map validationContext = new TreeMap<>(); validationContext.put(LOCATION_KEY, parameters.size() != 1 || parameter.isMany() ? parameter.getName() : ""); processParameter(operation, parameter, transferObjectType, exchange, validationContext, validationResults); - }); + } if (!validationResults.isEmpty()) { throw new ValidationException("Invalid request", validationResults); diff --git a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/RequestConverter.java b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/RequestConverter.java index 82b37ca5..736961b7 100644 --- a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/RequestConverter.java +++ b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/RequestConverter.java @@ -22,6 +22,7 @@ import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; +import hu.blackbelt.judo.dao.api.DAO; import hu.blackbelt.judo.dao.api.IdentifierProvider; import hu.blackbelt.judo.dao.api.Payload; import hu.blackbelt.judo.dao.api.PayloadValidator; @@ -45,6 +46,7 @@ import lombok.NonNull; import lombok.Singular; import lombok.extern.slf4j.Slf4j; +import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.*; import java.util.*; @@ -62,6 +64,8 @@ public class RequestConverter { private final Coercer coercer; + private final DAO dao; + private final PayloadValidator payloadValidator; private final TokenValidator filestoreTokenValidator; @@ -108,6 +112,7 @@ public class RequestConverter { public RequestConverter(@NonNull EClass transferObjectType, @NonNull AsmModel asmModel, @NonNull Coercer coercer, + DAO dao, @NonNull PayloadValidator payloadValidator, @NonNull @Singular Collection keepProperties, TokenValidator filestoreTokenValidator, @@ -119,6 +124,7 @@ public RequestConverter(@NonNull EClass transferObjectType, boolean throwValidationException) { this.transferObjectType = transferObjectType; this.coercer = coercer; + this.dao = dao; this.payloadValidator = payloadValidator; this.filestoreTokenValidator = filestoreTokenValidator; this.trimString = trimString; @@ -165,7 +171,6 @@ public Optional convert(final Map input, final Map validationResults, Map validationContext, Map feedbackContext) { - final String containerLocation = (String) validationContext.getOrDefault(LOCATION_KEY, ""); final Map currentContext = new TreeMap<>(validationContext); currentContext.put(LOCATION_KEY, (containerLocation.isEmpty() ? "" : containerLocation + "/") + ctx.getPathAsString()); @@ -173,22 +178,42 @@ private void processPayload(Payload instance, PayloadTraverser.PayloadTraverserC feedbackContext.put(LOCATION_KEY, currentContext.get(LOCATION_KEY)); feedbackContext.put(IS_ROOT_KEY, currentContext.get(IS_ROOT_KEY)); + EClass transferObjectType = ctx.getType(); + // Validate only elements which is contained only from root payload final boolean validate = !validatorProvider.getValidators().isEmpty() && ctx.getPath().stream().allMatch(e -> e.getReference().isContainment()); - final boolean ignoreInvalidValues = (Boolean) validationContext.getOrDefault(IGNORE_INVALID_VALUES_KEY, IGNORE_INVALID_VALUES_DEFAULT); - ctx.getType().getEAllAttributes().forEach(a -> processAttribute(instance, a, validationResults, validate, feedbackContext, ignoreInvalidValues)); + if (dao != null) { + // load default values if current payload is being "instantiated" + if (identifierProvider != null && !instance.containsKey(identifierProvider.getName())) { + dao.applyDefaultsOf(transferObjectType, instance); + } + } else { + log.warn("Default values cannot be loaded: DAO is not available for Request converter"); + } + + EList attributes = transferObjectType.getEAllAttributes(); + for (EAttribute attribute : attributes) { + processAttribute(instance, attribute, validationResults, validate, feedbackContext, ignoreInvalidValues); + } + + EList references = transferObjectType.getEAllReferences(); if ((Boolean) validationContext.getOrDefault(VALIDATE_FOR_CREATE_OR_UPDATE_KEY, VALIDATE_FOR_CREATE_OR_UPDATE_DEFAULT) && identifierProvider != null) { - ctx.getType().getEAllReferences().stream() - .filter(r -> isEmbedded(r) && !AsmUtils.isAllowedToCreateEmbeddedObject(r) && asmUtils.getMappedReference(r).filter(EReference::isContainment).isPresent()) - .forEach(c -> removeNonCreatableReferenceElements(instance, c)); + references.stream() + .filter(r -> isEmbedded(r) + && !AsmUtils.isAllowedToCreateEmbeddedObject(r) + && asmUtils.getMappedReference(r).filter(EReference::isContainment).isPresent()) + .forEach(c -> removeNonCreatableReferenceElements(instance, c)); } if (validate) { - ctx.getType().getEAllReferences().forEach(r -> processReference(instance, r, validationResults, currentContext, feedbackContext, ignoreInvalidValues)); + for (EReference reference : references) { + processReference(instance, reference, validationResults, currentContext, feedbackContext, ignoreInvalidValues); + } } - instance.entrySet().removeIf(entry -> ctx.getType().getEAllStructuralFeatures().stream().noneMatch(f -> Objects.equals(f.getName(), entry.getKey())) - && !keepProperties.contains(entry.getKey())); + instance.entrySet().removeIf(entry -> transferObjectType.getEAllStructuralFeatures().stream() + .noneMatch(f -> Objects.equals(f.getName(), entry.getKey())) + && !keepProperties.contains(entry.getKey())); } private void removeNonCreatableReferenceElements(Payload instance, EReference reference) { @@ -213,7 +238,6 @@ private void removeNonCreatableReferenceElements(Payload instance, EReference re } private void processReference(Payload instance, EReference reference, Collection validationResults, Map currentContext, Map feedbackContext, boolean ignoreInvalidValues) { - validateReferencedIdentifiers(instance, reference, validationResults, currentContext, feedbackContext); validationResults.addAll(payloadValidator.validateReference(reference, instance, currentContext, ignoreInvalidValues)); } diff --git a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/behaviours/GetTemplateCall.java b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/behaviours/GetTemplateCall.java index 05b69df8..211bd7af 100644 --- a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/behaviours/GetTemplateCall.java +++ b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/behaviours/GetTemplateCall.java @@ -20,20 +20,17 @@ * #L% */ -import hu.blackbelt.judo.dao.api.DAO; +import java.util.Map; + import hu.blackbelt.judo.dao.api.Payload; -import hu.blackbelt.judo.meta.asm.runtime.AsmModel; import hu.blackbelt.judo.meta.asm.runtime.AsmUtils; import hu.blackbelt.judo.runtime.core.dispatcher.CallInterceptorUtil; -import hu.blackbelt.judo.runtime.core.dispatcher.OperationCallInterceptorProvider; import lombok.Builder; import lombok.Getter; import lombok.NonNull; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EOperation; -import java.util.Map; - public class GetTemplateCall implements BehaviourCall { final ServiceContext serviceContext; @@ -58,10 +55,15 @@ public Object call(final Map exchange, final EOperation operatio .owner((EClass) serviceContext.getAsmUtils().getOwnerOfOperationWithDefaultBehaviour(operation).orElseThrow( () -> new IllegalArgumentException("Invalid model"))) .build()); - Payload result = null; + + Payload result; if (callInterceptorUtil.shouldCallOriginal()) { - result = serviceContext.getDao().getDefaultsOf(inputParameter.getOwner()); + result = Payload.empty(); + serviceContext.getDao().applyDefaultsOf(inputParameter.getOwner(), result); + } else { + result = null; } + return callInterceptorUtil.postCallInterceptors(inputParameter, result); } diff --git a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/behaviours/ValidateOperationInputCall.java b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/behaviours/ValidateOperationInputCall.java index 946af6de..658d738a 100644 --- a/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/behaviours/ValidateOperationInputCall.java +++ b/judo-runtime-core-dispatcher/src/main/java/hu/blackbelt/judo/runtime/core/dispatcher/behaviours/ValidateOperationInputCall.java @@ -20,29 +20,18 @@ * #L% */ -import hu.blackbelt.judo.dao.api.DAO; -import hu.blackbelt.judo.dao.api.IdentifierProvider; +import java.util.Map; + import hu.blackbelt.judo.dao.api.Payload; import hu.blackbelt.judo.dispatcher.api.Context; -import hu.blackbelt.judo.meta.asm.runtime.AsmModel; import hu.blackbelt.judo.meta.asm.runtime.AsmUtils; import hu.blackbelt.judo.runtime.core.dispatcher.CallInterceptorUtil; -import hu.blackbelt.judo.runtime.core.dispatcher.DefaultDispatcher; -import hu.blackbelt.judo.runtime.core.dispatcher.OperationCallInterceptorProvider; -import hu.blackbelt.mapper.api.Coercer; import lombok.Builder; import lombok.Getter; import lombok.NonNull; -import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EOperation; -import org.eclipse.emf.ecore.EReference; -import org.springframework.transaction.PlatformTransactionManager; -import java.util.*; - -import static com.google.common.base.Preconditions.checkArgument; -import static hu.blackbelt.judo.dao.api.Payload.asPayload; public class ValidateOperationInputCall extends AlwaysRollbackTransactionalBehaviourCall { @@ -74,12 +63,15 @@ public Object callInRollbackTransaction(Map exchange, EOperation callInterceptorUtil.preCallInterceptors( ValidateOperationInputCall.ValidateOperationCallPayload.builder() .owner(owner) - .input(asPayload((Map) exchange.get(inputParameterName))) + .input(Payload.asPayload((Map) exchange.get(inputParameterName))) .build()); - Payload result = null; + Payload result; if (callInterceptorUtil.shouldCallOriginal()) { - result = serviceContext.getDao().getDefaultsOf(owner.getEContainingClass()); + result = Payload.empty(); + serviceContext.getDao().applyDefaultsOf(owner.getEContainingClass(), result); + } else { + result = null; } return callInterceptorUtil.postCallInterceptors(inputParameter, result); diff --git a/judo-runtime-core-security/src/main/java/hu/blackbelt/judo/runtime/core/security/UserManagedWrappedDao.java b/judo-runtime-core-security/src/main/java/hu/blackbelt/judo/runtime/core/security/UserManagedWrappedDao.java index ef710e46..cf908fbf 100644 --- a/judo-runtime-core-security/src/main/java/hu/blackbelt/judo/runtime/core/security/UserManagedWrappedDao.java +++ b/judo-runtime-core-security/src/main/java/hu/blackbelt/judo/runtime/core/security/UserManagedWrappedDao.java @@ -84,6 +84,11 @@ public Payload getDefaultsOf(EClass clazz) { return delegatee.getDefaultsOf(clazz); } + @Override + public void applyDefaultsOf(EClass clazz, Payload payload) { + delegatee.applyDefaultsOf(clazz, payload); + } + @Override public Collection getRangeOf(EReference reference, Payload payload, QueryCustomizer queryCustomizer, boolean stateful) { return delegatee.getRangeOf(reference, payload, queryCustomizer, stateful); diff --git a/judo-runtime-core-validator/src/main/java/hu/blackbelt/judo/runtime/core/validator/DefaultPayloadValidator.java b/judo-runtime-core-validator/src/main/java/hu/blackbelt/judo/runtime/core/validator/DefaultPayloadValidator.java index c0e56711..5c04c9c2 100644 --- a/judo-runtime-core-validator/src/main/java/hu/blackbelt/judo/runtime/core/validator/DefaultPayloadValidator.java +++ b/judo-runtime-core-validator/src/main/java/hu/blackbelt/judo/runtime/core/validator/DefaultPayloadValidator.java @@ -20,6 +20,18 @@ * #L% */ +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.function.Supplier; + import com.google.common.collect.ImmutableMap; import hu.blackbelt.judo.dao.api.IdentifierProvider; import hu.blackbelt.judo.dao.api.Payload; @@ -33,26 +45,35 @@ import lombok.Builder; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; -import org.eclipse.emf.ecore.*; - -import java.util.*; -import java.util.function.*; - -import static hu.blackbelt.judo.runtime.core.validator.Validator.*; +import org.eclipse.emf.ecore.EAttribute; +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.emf.ecore.ETypedElement; + +import static hu.blackbelt.judo.runtime.core.validator.Validator.ERROR_INVALID_CONTENT; +import static hu.blackbelt.judo.runtime.core.validator.Validator.ERROR_MISSING_REQUIRED_ATTRIBUTE; +import static hu.blackbelt.judo.runtime.core.validator.Validator.ERROR_MISSING_REQUIRED_ATTRIBUTE_ON_ENTITY; +import static hu.blackbelt.judo.runtime.core.validator.Validator.ERROR_MISSING_REQUIRED_RELATION; +import static hu.blackbelt.judo.runtime.core.validator.Validator.ERROR_MISSING_REQUIRED_RELATION_ON_ENTITY; +import static hu.blackbelt.judo.runtime.core.validator.Validator.ERROR_NULL_ITEM_IS_NOT_SUPPORTED; +import static hu.blackbelt.judo.runtime.core.validator.Validator.ERROR_TOO_FEW_ITEMS; +import static hu.blackbelt.judo.runtime.core.validator.Validator.ERROR_TOO_MANY_ITEMS; +import static hu.blackbelt.judo.runtime.core.validator.Validator.SIZE_PARAMETER; +import static hu.blackbelt.judo.runtime.core.validator.Validator.addValidationError; @Slf4j public class DefaultPayloadValidator implements PayloadValidator { + public enum RequiredStringValidatorOption { ACCEPT_EMPTY, ACCEPT_NON_EMPTY } - private AsmModel asmModel; - private Coercer coercer; - private RequiredStringValidatorOption requiredStringValidatorOption; - @SuppressWarnings("rawtypes") - private IdentifierProvider identifierProvider; - private ValidatorProvider validatorProvider; - private AsmUtils asmUtils; + private final AsmModel asmModel; + private final Coercer coercer; + private final RequiredStringValidatorOption requiredStringValidatorOption; + private final IdentifierProvider identifierProvider; + private final ValidatorProvider validatorProvider; + private final AsmUtils asmUtils; public static final String GLOBAL_VALIDATION_CONTEXT = "globalValidationContext"; @@ -84,16 +105,16 @@ public enum RequiredStringValidatorOption { @Builder public DefaultPayloadValidator( - @NonNull AsmModel asmModel, - @NonNull Coercer coercer, - RequiredStringValidatorOption requiredStringValidatorOption, - IdentifierProvider identifierProvider, - ValidatorProvider validatorProvider) { + @NonNull AsmModel asmModel, + @NonNull Coercer coercer, + RequiredStringValidatorOption requiredStringValidatorOption, + IdentifierProvider identifierProvider, + ValidatorProvider validatorProvider) { this.asmModel = asmModel; this.coercer = coercer; - this.requiredStringValidatorOption = Optional.ofNullable(requiredStringValidatorOption).orElse(this.requiredStringValidatorOption); - this.identifierProvider = Optional.ofNullable(identifierProvider).orElse(this.identifierProvider); - this.validatorProvider = Optional.ofNullable(validatorProvider).orElse(this.validatorProvider); + this.requiredStringValidatorOption = requiredStringValidatorOption; + this.identifierProvider = identifierProvider; + this.validatorProvider = validatorProvider; this.asmUtils = new AsmUtils(asmModel.getResourceSet()); } @@ -106,12 +127,12 @@ public List validatePayload(final EClass transferObjectType, f try { PayloadTraverser.builder() - .predicate((reference) -> (Boolean) validationContext.getOrDefault(VALIDATE_FOR_CREATE_OR_UPDATE_KEY, VALIDATE_FOR_CREATE_OR_UPDATE_DEFAULT) - ? asmUtils.getMappedReference(reference).map(e -> !e.isDerived()).orElse(false) - : !(Boolean) validationContext.getOrDefault(NO_TRAVERSE_KEY, NO_TRAVERSE_DEFAULT)) - .processor((instance, ctx) -> processPayload(instance, ctx, validationResults, validationContext)) - .build() - .traverse(payload, transferObjectType); + .predicate((reference) -> (Boolean) validationContext.getOrDefault(VALIDATE_FOR_CREATE_OR_UPDATE_KEY, VALIDATE_FOR_CREATE_OR_UPDATE_DEFAULT) + ? asmUtils.getMappedReference(reference).map(e -> !e.isDerived()).orElse(false) + : !(Boolean) validationContext.getOrDefault(NO_TRAVERSE_KEY, NO_TRAVERSE_DEFAULT)) + .processor((instance, ctx) -> processPayload(instance, ctx, validationResults, validationContext)) + .build() + .traverse(payload, transferObjectType); } catch (IllegalArgumentException ex) { log.debug("Invalid payload", ex); } @@ -180,16 +201,14 @@ public List validateReference(final EReference reference, fina boolean isRootElement = (Boolean) validationContext.get(IS_ROOT_KEY); if (validateMissingFeatures) { - boolean isReferenceMissingAndChangeableAndDefaultValueAvailable = !instance.containsKey(reference.getName()) && reference.isChangeable() && AsmUtils.getExtensionAnnotationValue(reference, "default", false).isPresent(); - validateMissingFeatures = getIdentifier(instance) == null && !isReferenceMissingAndChangeableAndDefaultValueAvailable; - if(!validateRootMissingFeatures && isRootElement) { + validateMissingFeatures = getIdentifier(instance) == null; + if (!validateRootMissingFeatures && isRootElement) { validateMissingFeatures = false; } } if (reference.isMany()) { if (!ignoreInvalidValues && value instanceof Collection) { - @SuppressWarnings("rawtypes") final int size = ((Collection) value).size(); if (size < reference.getLowerBound()) { addValidationError( @@ -215,8 +234,7 @@ public List validateReference(final EReference reference, fina ); } int idx = 0; - for (@SuppressWarnings("rawtypes") - Iterator it = ((Collection) value).iterator(); it.hasNext(); idx++) { + for (Iterator it = ((Collection) value).iterator(); it.hasNext(); idx++) { final Map currentItemContext = new TreeMap<>(currentContext); currentItemContext.put(LOCATION_KEY, currentContext.get(LOCATION_KEY) + "[" + idx + "]"); final Object item = it.next(); @@ -233,9 +251,11 @@ public List validateReference(final EReference reference, fina } else if (!(item instanceof Payload)) { throw new IllegalStateException("Item must be a Payload"); } else { - validatorProvider.getValidators().stream() - .filter(v -> v.isApplicable(reference)) - .forEach(v -> validationResults.addAll(v.validateValue(instance, reference, item, currentItemContext))); + for (Validator validator : validatorProvider.getValidators()) { + if (validator.isApplicable(reference)) { + validationResults.addAll(validator.validateValue(instance, reference, item, currentItemContext)); + } + } } } } else if (value != null && !(value instanceof Collection)) { @@ -263,23 +283,26 @@ public List validateReference(final EReference reference, fina } else if (value != null && !(value instanceof Payload)) { throw new IllegalStateException("Item must be a Payload"); } else if (!ignoreInvalidValues && value != null) { - validatorProvider.getValidators().stream() - .filter(v -> v.isApplicable(reference)) - .forEach(v -> validationResults.addAll(v.validateValue(instance, reference, value, currentContext))); + for (Validator validator : validatorProvider.getValidators()) { + if (validator.isApplicable(reference)) { + validationResults.addAll(validator.validateValue(instance, reference, value, currentContext)); + } + } } } final Optional mappedReference = asmUtils.getMappedReference(reference); - final boolean validateForCreate = createReference != null - ? mappedReference - .map(mr -> asmUtils.getMappedReference(createReference) - .map(EReference::getEOpposite) - .filter(mappedCreateReferenceOpposite -> AsmUtils.equals(mappedCreateReferenceOpposite, mr)) - .isEmpty()) - .orElse(false) - : false; - - if (reference.isRequired() && (validateMissingFeatures || instance.containsKey(reference.getName())) && (createReference == null || mappedReference.isEmpty() || validateForCreate) && value == null) { + + Supplier validateForCreate = + () -> asmUtils.getMappedReference(createReference) + .map(EReference::getEOpposite) + .filter(mappedCreateReferenceOpposite -> AsmUtils.equals(mappedCreateReferenceOpposite, mappedReference.get())) + .isEmpty(); + + if (value == null + && (reference.isRequired() || mappedReference.map(ETypedElement::isRequired).orElse(false)) + && (validateMissingFeatures || instance.containsKey(reference.getName())) + && (createReference == null || mappedReference.isEmpty() || validateForCreate.get())) { addValidationError( ImmutableMap.of( Validator.FEATURE_KEY, REFERENCE_TO_MODEL_TYPE.apply(reference), @@ -287,7 +310,7 @@ public List validateReference(final EReference reference, fina ), currentContext.get(LOCATION_KEY), validationResults, - ERROR_MISSING_REQUIRED_RELATION + reference.isRequired() ? ERROR_MISSING_REQUIRED_RELATION : ERROR_MISSING_REQUIRED_RELATION_ON_ENTITY ); } return validationResults; @@ -299,19 +322,18 @@ public Collection validateAttribute(final EAttribute attribute boolean validateRootMissingFeatures = (Boolean) validationContext.getOrDefault(VALIDATE_ROOT_MISSING_FEATURES_KEY, VALIDATE_ROOT_MISSING_FEATURES_DEFAULT); boolean isRootElement = (Boolean) validationContext.get(IS_ROOT_KEY); - if (validateMissingFeatures ) { - boolean isAttributeMissingAndChangeableAndDefaultValueAvailable = !instance.containsKey(attribute.getName()) && attribute.isChangeable() && AsmUtils.getExtensionAnnotationValue(attribute, "default", false).isPresent(); - validateMissingFeatures = getIdentifier(instance) == null && !isAttributeMissingAndChangeableAndDefaultValueAvailable; - if(!validateRootMissingFeatures && isRootElement) { + if (validateMissingFeatures) { + validateMissingFeatures = getIdentifier(instance) == null; + if (!validateRootMissingFeatures && isRootElement) { validateMissingFeatures = false; } } final List validationResults = new ArrayList<>(); - if ((attribute.isRequired() || asmUtils.getMappedAttribute(attribute).map(EAttribute::isRequired).orElse(false)) && - (validateMissingFeatures || instance.containsKey(attribute.getName())) && - value == null) { + if (value == null + && (attribute.isRequired() || asmUtils.getMappedAttribute(attribute).map(EAttribute::isRequired).orElse(false)) + && (validateMissingFeatures || instance.containsKey(attribute.getName()))) { addValidationError( ImmutableMap.of( @@ -323,12 +345,12 @@ public Collection validateAttribute(final EAttribute attribute ERROR_MISSING_REQUIRED_ATTRIBUTE ); } - if (AsmUtils.isString(attribute.getEAttributeType()) && - (attribute.isRequired() || asmUtils.getMappedAttribute(attribute).map(EAttribute::isRequired).orElse(false)) && - (validateMissingFeatures || instance.containsKey(attribute.getName())) && - value != null && - RequiredStringValidatorOption.ACCEPT_NON_EMPTY.equals(requiredStringValidatorOption) && - ((String) value).isEmpty()) { + if (value != null + && AsmUtils.isString(attribute.getEAttributeType()) + && RequiredStringValidatorOption.ACCEPT_NON_EMPTY.equals(requiredStringValidatorOption) + && ((String) value).isEmpty() + && (attribute.isRequired() || asmUtils.getMappedAttribute(attribute).map(EAttribute::isRequired).orElse(false)) + && (validateMissingFeatures || instance.containsKey(attribute.getName()))) { addValidationError( ImmutableMap.of( @@ -337,20 +359,21 @@ public Collection validateAttribute(final EAttribute attribute ), validationContext.get(LOCATION_KEY), validationResults, - ERROR_MISSING_REQUIRED_ATTRIBUTE + attribute.isRequired() ? ERROR_MISSING_REQUIRED_ATTRIBUTE : ERROR_MISSING_REQUIRED_ATTRIBUTE_ON_ENTITY ); } if (value != null) { - validatorProvider.getValidators().stream() - .filter(v -> v.isApplicable(attribute)) - .forEach(v -> validationResults.addAll(v.validateValue(instance, attribute, value, validationContext))); + for (Validator validator : validatorProvider.getValidators()) { + if (validator.isApplicable(attribute)) { + validationResults.addAll(validator.validateValue(instance, attribute, value, validationContext)); + } + } } return validationResults; } - private Object getIdentifier(Payload instance) { Object identifier = null; if (identifierProvider != null) { @@ -358,4 +381,5 @@ private Object getIdentifier(Payload instance) { } return identifier; } + } diff --git a/judo-runtime-core-validator/src/main/java/hu/blackbelt/judo/runtime/core/validator/Validator.java b/judo-runtime-core-validator/src/main/java/hu/blackbelt/judo/runtime/core/validator/Validator.java index 71a646d1..4cf6afb2 100644 --- a/judo-runtime-core-validator/src/main/java/hu/blackbelt/judo/runtime/core/validator/Validator.java +++ b/judo-runtime-core-validator/src/main/java/hu/blackbelt/judo/runtime/core/validator/Validator.java @@ -39,7 +39,9 @@ public interface Validator { String ERROR_TOO_FEW_ITEMS = "TOO_FEW_ITEMS"; String ERROR_TOO_MANY_ITEMS = "TOO_MANY_ITEMS"; String ERROR_MISSING_REQUIRED_ATTRIBUTE = "MISSING_REQUIRED_ATTRIBUTE"; + String ERROR_MISSING_REQUIRED_ATTRIBUTE_ON_ENTITY = "MISSING_REQUIRED_ATTRIBUTE_ON_ENTITY"; String ERROR_MISSING_REQUIRED_RELATION = "MISSING_REQUIRED_RELATION"; + String ERROR_MISSING_REQUIRED_RELATION_ON_ENTITY = "MISSING_REQUIRED_RELATION_ON_ENTITY"; String ERROR_INVALID_CONTENT = "INVALID_CONTENT"; String ERROR_NULL_ITEM_IS_NOT_SUPPORTED = "NULL_ITEM_IS_NOT_SUPPORTED"; String ERROR_CONVERSION_FAILED = "CONVERSION_FAILED"; diff --git a/judo-runtime-core/src/main/java/hu/blackbelt/judo/runtime/core/PayloadTraverser.java b/judo-runtime-core/src/main/java/hu/blackbelt/judo/runtime/core/PayloadTraverser.java index 912eee7d..e412a3ea 100644 --- a/judo-runtime-core/src/main/java/hu/blackbelt/judo/runtime/core/PayloadTraverser.java +++ b/judo-runtime-core/src/main/java/hu/blackbelt/judo/runtime/core/PayloadTraverser.java @@ -23,6 +23,7 @@ import com.google.common.collect.ImmutableList; import hu.blackbelt.judo.dao.api.Payload; import lombok.*; +import lombok.extern.slf4j.Slf4j; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; @@ -37,6 +38,7 @@ @Getter @Builder +@Slf4j public class PayloadTraverser { @NonNull @@ -45,8 +47,6 @@ public class PayloadTraverser { @NonNull private Predicate predicate; - private static final Predicate IS_COLLECTION = ETypedElement::isMany; - public Payload traverse(final Payload payload, final EClass transferObjectType) { return traverse(payload, PayloadTraverserContext .builder() @@ -88,10 +88,16 @@ private Payload traverse(final Payload payload, final PayloadTraverserContext ct private static Collector>> toReferencePayloadMapOfPayloadCollection(Payload payload) { return Collectors.toMap(Function.identity(), (r) -> { - if (IS_COLLECTION.test(r)) { - return payload.getAsCollectionPayload(r.getName()); + String referenceName = r.getName(); + Object payloadElement = payload.get(referenceName); + if (payloadElement instanceof Collection collection) { + return collection; + } else if (payloadElement instanceof Payload payloadInstance) { + return ImmutableList.of(payloadInstance); } else { - return ImmutableList.of(payload.getAsPayload(r.getName())); + log.warn("Reference found with name '{}' cannot be traversed because its type in the payload is neither a Collection or a Payload: {}", + referenceName, payloadElement.getClass().getName()); + return ImmutableList.of(); } }); } diff --git a/pom.xml b/pom.xml index 08b1887a..1822853c 100644 --- a/pom.xml +++ b/pom.xml @@ -60,12 +60,12 @@ 1.0.3.20241212_103107_7309e96c_develop 1.0.1.20241202_042226_cdd8af59_develop - 1.0.4.20241127_150110_f05e3ff8_develop - 1.0.4.20241202_042423_0bcd40fc_develop + 1.0.4.20250119_185426_46563767_feature_JNG_6082_Refactor_required_validation_and_default_value_processing + 1.0.4.20250119_215903_575a8dad_feature_JNG_6082_Refactor_required_validation_and_default_value_processing 1.0.3.20241127_150146_84f2988d_develop - 1.1.3.20241202_042406_1861c263_develop + 1.1.3.20250119_215906_ba8080a3_feature_JNG_6082_Refactor_required_validation_and_default_value_processing - 1.1.6.20241215_175758_6567f8a2_develop + 1.1.6.20250119_215947_bd60b955_feature_JNG_6082_Refactor_required_validation_and_default_value_processing 1.0.4.20241127_150716_35788c69_develop 1.3.1.20241127_155448_e9885d1a_develop