diff --git a/openbas-api/src/main/java/io/openbas/rest/atomic_testing/AtomicTestingApi.java b/openbas-api/src/main/java/io/openbas/rest/atomic_testing/AtomicTestingApi.java index 1551efdbe7..58b97cbcbf 100644 --- a/openbas-api/src/main/java/io/openbas/rest/atomic_testing/AtomicTestingApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/atomic_testing/AtomicTestingApi.java @@ -8,15 +8,16 @@ import io.openbas.rest.atomic_testing.form.InjectResultDTO; import io.openbas.rest.exception.ElementNotFoundException; import io.openbas.rest.helper.RestBehavior; +import io.openbas.rest.inject.output.AtomicTestingOutput; import io.openbas.service.AtomicTestingService; import io.openbas.utils.AtomicTestingMapper; import io.openbas.utils.pagination.SearchPaginationInput; -import jakarta.transaction.Transactional; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -33,16 +34,10 @@ public class AtomicTestingApi extends RestBehavior { private final InjectExpectationService injectExpectationService; @PostMapping("/search") - public Page findAllAtomicTestings( + @Transactional(readOnly = true) + public Page findAllAtomicTestings( @RequestBody @Valid final SearchPaginationInput searchPaginationInput) { - return this.atomicTestingService.findAllAtomicTestings(searchPaginationInput) - .map(inject -> AtomicTestingMapper.toDto( - inject, getTargets( - inject.getTeams(), - inject.getAssets(), - inject.getAssetGroups() - ) - )); + return this.atomicTestingService.findAllAtomicTestings(searchPaginationInput); } @GetMapping("/{injectId}") @@ -53,7 +48,7 @@ public InjectResultDTO findAtomicTesting(@PathVariable String injectId) { } @PostMapping() - @Transactional(rollbackOn = Exception.class) + @Transactional(rollbackFor = Exception.class) public InjectResultDTO createAtomicTesting(@Valid @RequestBody AtomicTestingInput input) { Inject inject = this.atomicTestingService.createOrUpdate(input, null); return AtomicTestingMapper.toDto( @@ -66,7 +61,7 @@ inject, getTargets( } @PutMapping("/{injectId}") - @Transactional(rollbackOn = Exception.class) + @Transactional(rollbackFor = Exception.class) public InjectResultDTO updateAtomicTesting( @PathVariable @NotBlank final String injectId, @Valid @RequestBody final AtomicTestingInput input) { @@ -100,7 +95,7 @@ public List findTargetResult( } @PutMapping("/{injectId}/tags") - @Transactional(rollbackOn = Exception.class) + @Transactional(rollbackFor = Exception.class) public InjectResultDTO updateAtomicTestingTags( @PathVariable @NotBlank final String injectId, @Valid @RequestBody final AtomicTestingUpdateTagsInput input) { diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/output/AtomicTestingOutput.java b/openbas-api/src/main/java/io/openbas/rest/inject/output/AtomicTestingOutput.java new file mode 100644 index 0000000000..d434db7114 --- /dev/null +++ b/openbas-api/src/main/java/io/openbas/rest/inject/output/AtomicTestingOutput.java @@ -0,0 +1,101 @@ +package io.openbas.rest.inject.output; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.openbas.database.model.InjectStatus; +import io.openbas.database.model.InjectorContract; +import io.openbas.rest.atomic_testing.form.InjectTargetWithResult; +import io.openbas.utils.AtomicTestingMapper; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import lombok.Getter; + +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static io.openbas.database.model.InjectStatus.draftInjectStatus; +import static lombok.AccessLevel.NONE; + +@Data +public class AtomicTestingOutput { + + @JsonProperty("inject_id") + @NotBlank + private String id; + + @JsonProperty("inject_title") + @NotBlank + private String title; + + @JsonProperty("inject_updated_at") + @NotNull + private Instant updatedAt; + + @JsonProperty("inject_type") + public String injectType; + + @JsonProperty("inject_injector_contract") + private InjectorContract injectorContract; + + @Getter(NONE) + @JsonProperty("inject_status") + private InjectStatus status; + + public InjectStatus getStatus() { + if (status == null) { + return draftInjectStatus(); + } + return status; + } + + @JsonProperty("inject_teams") + @NotNull + private List teams; + + @JsonProperty("inject_assets") + @NotNull + private List assets; + + @JsonProperty("inject_asset_groups") + @NotNull + private List assetGroups; + + @JsonProperty("inject_expectations") + @NotNull + private List expectations; + + // Pre Calcul + + @JsonProperty("inject_targets") + private List targets; + + @JsonProperty("inject_expectation_results") + private List expectationResultByTypes = new ArrayList<>(); + + public AtomicTestingOutput( + String id, + String title, + Instant updatedAt, + String injectType, + InjectorContract injectorContract, + InjectStatus injectStatus, + String[] injectExpectations, + String[] teams, + String[] assets, + String[] assetGroups) { + this.id = id; + this.title = title; + this.updatedAt = updatedAt; + this.injectType = injectType; + this.injectorContract = injectorContract; + this.status = injectStatus; + this.expectations = injectExpectations != null ? new ArrayList<>(Arrays.asList(injectExpectations)) : new ArrayList<>(); + + this.teams = teams != null ? new ArrayList<>(Arrays.asList(teams)) : new ArrayList<>(); + this.assets = assets != null ? new ArrayList<>(Arrays.asList(assets)) : new ArrayList<>(); + this.assetGroups = assetGroups != null ? new ArrayList<>(Arrays.asList(assetGroups)) : new ArrayList<>(); + } + +} diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/output/InjectOutput.java b/openbas-api/src/main/java/io/openbas/rest/inject/output/InjectOutput.java index e987d0c00f..3b86e14f05 100644 --- a/openbas-api/src/main/java/io/openbas/rest/inject/output/InjectOutput.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/output/InjectOutput.java @@ -9,9 +9,7 @@ import jakarta.validation.constraints.NotNull; import lombok.Data; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; +import java.util.*; @Data public class InjectOutput { @@ -50,7 +48,13 @@ public class InjectOutput { public String injectType; @JsonProperty("inject_teams") - private Set teams; + private List teams; + + @JsonProperty("inject_assets") + private List assets; + + @JsonProperty("inject_asset_groups") + private List assetGroups; @JsonProperty("inject_content") private ObjectNode content; @@ -78,16 +82,21 @@ public InjectOutput( this.dependsDuration = dependsDuration; this.injectorContract = injectorContract; this.tags = tags != null ? new HashSet<>(Arrays.asList(tags)) : new HashSet<>(); + + this.teams = teams != null ? new ArrayList<>(Arrays.asList(teams)) : new ArrayList<>(); + this.assets = assets != null ? new ArrayList<>(Arrays.asList(assets)) : new ArrayList<>(); + this.assetGroups = assetGroups != null ? new ArrayList<>(Arrays.asList(assetGroups)) : new ArrayList<>(); + this.isReady = InjectModelHelper.isReady( injectorContract, content, allTeams, - teams != null ? new HashSet<>(Arrays.asList(teams)) : new HashSet<>(), - assets != null ? new HashSet<>(Arrays.asList(assets)) : new HashSet<>(), - assetGroups != null ? new HashSet<>(Arrays.asList(assetGroups)) : new HashSet<>() + this.teams, + this.assets, + this.assetGroups ); this.injectType = injectType; - this.teams = teams != null ? new HashSet<>(Arrays.asList(teams)) : new HashSet<>(); + this.teams = teams != null ? new ArrayList<>(Arrays.asList(teams)) : new ArrayList<>(); this.content = content; } } diff --git a/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java b/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java index 3d21fa0323..a6ef77392d 100644 --- a/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java +++ b/openbas-api/src/main/java/io/openbas/service/AtomicTestingService.java @@ -6,237 +6,327 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.openbas.database.model.*; +import io.openbas.database.raw.RawAsset; +import io.openbas.database.raw.RawAssetGroup; +import io.openbas.database.raw.RawInjectExpectation; +import io.openbas.database.raw.RawTeam; import io.openbas.database.repository.*; -import io.openbas.execution.ExecutionContextService; -import io.openbas.execution.Executor; import io.openbas.injector_contract.ContractType; import io.openbas.rest.atomic_testing.form.AtomicTestingInput; import io.openbas.rest.atomic_testing.form.AtomicTestingUpdateTagsInput; +import io.openbas.rest.inject.output.AtomicTestingOutput; import io.openbas.utils.pagination.SearchPaginationInput; import jakarta.annotation.Resource; -import jakarta.persistence.criteria.Predicate; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.Tuple; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.*; import jakarta.transaction.Transactional; -import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; import lombok.extern.java.Log; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import java.time.Instant; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; import java.util.stream.StreamSupport; import static io.openbas.config.SessionHelper.currentUser; +import static io.openbas.database.criteria.InjectCriteria.countQuery; import static io.openbas.helper.StreamHelper.fromIterable; import static io.openbas.helper.StreamHelper.iterableToSet; -import static io.openbas.utils.pagination.PaginationUtils.buildPaginationJPA; +import static io.openbas.utils.AtomicTestingUtils.getRawExpectationResultByTypes; +import static io.openbas.utils.AtomicTestingUtils.getTargetsFromRaw; +import static io.openbas.utils.JpaUtils.createJoinArrayAggOnId; +import static io.openbas.utils.JpaUtils.createLeftJoin; +import static io.openbas.utils.pagination.PaginationUtils.buildPaginationCriteriaBuilder; +import static io.openbas.utils.pagination.SortUtilsCriteriaBuilder.toSortCriteriaBuilder; @Service @Log +@RequiredArgsConstructor public class AtomicTestingService { - @Resource - protected ObjectMapper mapper; - - private Executor executor; - private ExecutionContextService executionContextService; - - private AssetGroupRepository assetGroupRepository; - - private AssetRepository assetRepository; - private InjectRepository injectRepository; - private InjectStatusRepository injectStatusRepository; - private InjectorContractRepository injectorContractRepository; - private InjectDocumentRepository injectDocumentRepository; - private UserRepository userRepository; - private TeamRepository teamRepository; - private TagRepository tagRepository; - private DocumentRepository documentRepository; - - @Autowired - public void setExecutor(@NotNull final Executor executor) { - this.executor = executor; - } - - @Autowired - public void setExecutionContextService(@NotNull final ExecutionContextService executionContextService) { - this.executionContextService = executionContextService; - } - - @Autowired - public void setInjectRepository(@NotNull final InjectRepository injectRepository) { - this.injectRepository = injectRepository; - } - - @Autowired - public void setInjectStatusRepository(@NotNull final InjectStatusRepository injectStatusRepository) { - this.injectStatusRepository = injectStatusRepository; - } - - @Autowired - public void setAssetRepository(@NotNull final AssetRepository assetRepository) { - this.assetRepository = assetRepository; - } - @Autowired - public void setAssetGroupRepository(@NotNull final AssetGroupRepository assetGroupRepository) { - this.assetGroupRepository = assetGroupRepository; + @Resource + protected ObjectMapper mapper; + + private final AssetGroupRepository assetGroupRepository; + private final AssetRepository assetRepository; + private final InjectRepository injectRepository; + private final InjectExpectationRepository injectExpectationRepository; + private final InjectStatusRepository injectStatusRepository; + private final InjectorContractRepository injectorContractRepository; + private final InjectDocumentRepository injectDocumentRepository; + private final UserRepository userRepository; + private final TeamRepository teamRepository; + private final TagRepository tagRepository; + private final DocumentRepository documentRepository; + + @PersistenceContext + private EntityManager entityManager; + + public Optional findById(String injectId) { + return injectRepository.findWithStatusById(injectId); + } + + @Transactional + public Inject createOrUpdate(AtomicTestingInput input, String injectId) { + Inject injectToSave = new Inject(); + if (injectId != null) { + injectToSave = injectRepository.findById(injectId).orElseThrow(); } - @Autowired - public void setInjectDocumentRepository(@NotNull final InjectDocumentRepository injectDocumentRepository) { - this.injectDocumentRepository = injectDocumentRepository; - } - - @Autowired - public void setUserRepository(@NotNull final UserRepository userRepository) { - this.userRepository = userRepository; - } - - @Autowired - public void setTeamRepository(@NotNull final TeamRepository teamRepository) { - this.teamRepository = teamRepository; - } - - @Autowired - public void setTagRepository(@NotNull final TagRepository tagRepository) { - this.tagRepository = tagRepository; - } - - @Autowired - public void setDocumentRepository(@NotNull final DocumentRepository documentRepository) { - this.documentRepository = documentRepository; - } - - @Autowired - public void setInjectorContractRepository(@NotNull final InjectorContractRepository injectorContractRepository) { - this.injectorContractRepository = injectorContractRepository; - } - - public Page findAllAtomicTestings(SearchPaginationInput searchPaginationInput) { - Specification customSpec = Specification.where((root, query, cb) -> { - Predicate predicate = cb.conjunction(); - predicate = cb.and(predicate, cb.isNull(root.get("scenario"))); - predicate = cb.and(predicate, cb.isNull(root.get("exercise"))); - return predicate; - }); - return buildPaginationJPA( - (Specification specification, Pageable pageable) -> injectRepository.findAll( - specification.and(customSpec), pageable), - searchPaginationInput, - Inject.class - ); - } - - public Optional findById(String injectId) { - return injectRepository.findWithStatusById(injectId); - } - - @Transactional - public Inject createOrUpdate(AtomicTestingInput input, String injectId) { - Inject injectToSave = new Inject(); - if (injectId != null) { - injectToSave = injectRepository.findById(injectId).orElseThrow(); - } - - InjectorContract injectorContract = injectorContractRepository.findById(input.getInjectorContract()).orElseThrow(); - ObjectNode finalContent = input.getContent(); - // Set expectations - if (injectId == null) { - if (input.getContent() == null || input.getContent().get("expectations") == null || input.getContent().get("expectations").isEmpty()) { - try { - JsonNode jsonNode = mapper.readTree(injectorContract.getContent()); - List contractElements = StreamSupport.stream(jsonNode.get("fields").spliterator(), false).filter(contractElement -> contractElement.get("type").asText().equals(ContractType.Expectation.name().toLowerCase())).toList(); - if (!contractElements.isEmpty()) { - JsonNode contractElement = contractElements.getFirst(); - if (!contractElement.get("predefinedExpectations").isNull() && !contractElement.get("predefinedExpectations").isEmpty()) { - finalContent = finalContent != null ? finalContent : mapper.createObjectNode(); - ArrayNode predefinedExpectations = mapper.createArrayNode(); - StreamSupport.stream(contractElement.get("predefinedExpectations").spliterator(), false).forEach(predefinedExpectation -> { - ObjectNode newExpectation = predefinedExpectation.deepCopy(); - newExpectation.put("expectation_score", 100); - predefinedExpectations.add(newExpectation); - }); - finalContent.put("expectations", predefinedExpectations); - } - } - } catch (JsonProcessingException e) { - log.severe("Cannot open injector contract"); - } + InjectorContract injectorContract = injectorContractRepository.findById(input.getInjectorContract()).orElseThrow(); + ObjectNode finalContent = input.getContent(); + // Set expectations + if (injectId == null) { + if (input.getContent() == null || input.getContent().get("expectations") == null || input.getContent() + .get("expectations").isEmpty()) { + try { + JsonNode jsonNode = mapper.readTree(injectorContract.getContent()); + List contractElements = StreamSupport.stream(jsonNode.get("fields").spliterator(), false).filter( + contractElement -> contractElement.get("type").asText() + .equals(ContractType.Expectation.name().toLowerCase())).toList(); + if (!contractElements.isEmpty()) { + JsonNode contractElement = contractElements.getFirst(); + if (!contractElement.get("predefinedExpectations").isNull() && !contractElement.get( + "predefinedExpectations").isEmpty()) { + finalContent = finalContent != null ? finalContent : mapper.createObjectNode(); + ArrayNode predefinedExpectations = mapper.createArrayNode(); + StreamSupport.stream(contractElement.get("predefinedExpectations").spliterator(), false) + .forEach(predefinedExpectation -> { + ObjectNode newExpectation = predefinedExpectation.deepCopy(); + newExpectation.put("expectation_score", 100); + predefinedExpectations.add(newExpectation); + }); + finalContent.put("expectations", predefinedExpectations); } + } + } catch (JsonProcessingException e) { + log.severe("Cannot open injector contract"); } - injectToSave.setTitle(input.getTitle()); - injectToSave.setContent(finalContent); - injectToSave.setInjectorContract(injectorContract); - injectToSave.setAllTeams(input.isAllTeams()); - injectToSave.setDescription(input.getDescription()); - injectToSave.setDependsDuration(0L); - injectToSave.setUser(userRepository.findById(currentUser().getId()).orElseThrow()); - injectToSave.setExercise(null); - - // Set dependencies - injectToSave.setDependsOn(null); - injectToSave.setTeams(fromIterable(teamRepository.findAllById(input.getTeams()))); - injectToSave.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); - injectToSave.setAssets(fromIterable(this.assetRepository.findAllById(input.getAssets()))); - injectToSave.setAssetGroups(fromIterable(this.assetGroupRepository.findAllById(input.getAssetGroups()))); - - List previousDocumentIds = injectToSave - .getDocuments() - .stream() - .map(InjectDocument::getDocument) - .map(Document::getId) - .toList(); - - Inject finalInjectToSave = injectToSave; - List injectDocuments = input.getDocuments().stream() - .map(i -> { - if (!previousDocumentIds.contains(i.getDocumentId())) { - InjectDocument injectDocument = new InjectDocument(); - injectDocument.setInject(finalInjectToSave); - injectDocument.setDocument(documentRepository.findById(i.getDocumentId()).orElseThrow()); - injectDocument.setAttached(i.isAttached()); - return injectDocument; - } - return null; - }).filter(Objects::nonNull).toList(); - injectToSave.getDocuments().addAll(injectDocuments); - return injectRepository.save(injectToSave); + } } - - public Inject updateAtomicTestingTags(String injectId, AtomicTestingUpdateTagsInput input) { - - Inject inject = injectRepository.findById(injectId).orElseThrow(); - inject.setTags(iterableToSet(this.tagRepository.findAllById(input.getTagIds()))); - - return injectRepository.save(inject); + injectToSave.setTitle(input.getTitle()); + injectToSave.setContent(finalContent); + injectToSave.setInjectorContract(injectorContract); + injectToSave.setAllTeams(input.isAllTeams()); + injectToSave.setDescription(input.getDescription()); + injectToSave.setDependsDuration(0L); + injectToSave.setUser(userRepository.findById(currentUser().getId()).orElseThrow()); + injectToSave.setExercise(null); + + // Set dependencies + injectToSave.setDependsOn(null); + injectToSave.setTeams(fromIterable(teamRepository.findAllById(input.getTeams()))); + injectToSave.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); + injectToSave.setAssets(fromIterable(this.assetRepository.findAllById(input.getAssets()))); + injectToSave.setAssetGroups(fromIterable(this.assetGroupRepository.findAllById(input.getAssetGroups()))); + + List previousDocumentIds = injectToSave + .getDocuments() + .stream() + .map(InjectDocument::getDocument) + .map(Document::getId) + .toList(); + + Inject finalInjectToSave = injectToSave; + List injectDocuments = input.getDocuments().stream() + .map(i -> { + if (!previousDocumentIds.contains(i.getDocumentId())) { + InjectDocument injectDocument = new InjectDocument(); + injectDocument.setInject(finalInjectToSave); + injectDocument.setDocument(documentRepository.findById(i.getDocumentId()).orElseThrow()); + injectDocument.setAttached(i.isAttached()); + return injectDocument; + } + return null; + }).filter(Objects::nonNull).toList(); + injectToSave.getDocuments().addAll(injectDocuments); + return injectRepository.save(injectToSave); + } + + public Inject updateAtomicTestingTags(String injectId, AtomicTestingUpdateTagsInput input) { + + Inject inject = injectRepository.findById(injectId).orElseThrow(); + inject.setTags(iterableToSet(this.tagRepository.findAllById(input.getTagIds()))); + + return injectRepository.save(inject); + } + + @Transactional + public Inject tryInject(String injectId) { + Inject inject = injectRepository.findById(injectId).orElseThrow(); + + // Reset injects outcome, communications and expectations + inject.clean(); + inject.setUpdatedAt(Instant.now()); + + // New inject status + InjectStatus injectStatus = new InjectStatus(); + injectStatus.setInject(inject); + injectStatus.setTrackingSentDate(Instant.now()); + injectStatus.setName(ExecutionStatus.QUEUING); + this.injectStatusRepository.save(injectStatus); + + // Return inject + return this.injectRepository.save(inject); + } + + @Transactional + public void deleteAtomicTesting(String injectId) { + injectDocumentRepository.deleteDocumentsFromInject(injectId); + injectRepository.deleteById(injectId); + } + + // -- PAGINATION -- + + public Page findAllAtomicTestings(SearchPaginationInput searchPaginationInput) { + Specification customSpec = Specification.where((root, query, cb) -> { + Predicate predicate = cb.conjunction(); + predicate = cb.and(predicate, cb.isNull(root.get("scenario"))); + predicate = cb.and(predicate, cb.isNull(root.get("exercise"))); + return predicate; + }); + return buildPaginationCriteriaBuilder( + (Specification specification, Pageable pageable) -> this.atomicTestings( + specification.and(customSpec), pageable), + searchPaginationInput, + Inject.class + ); + } + + + public Page atomicTestings(Specification specification, Pageable pageable) { + CriteriaBuilder cb = this.entityManager.getCriteriaBuilder(); + + CriteriaQuery cq = cb.createTupleQuery(); + Root injectRoot = cq.from(Inject.class); + selectForAtomicTesting(cb, cq, injectRoot); + + // -- Text Search and Filters -- + if (specification != null) { + Predicate predicate = specification.toPredicate(injectRoot, cq, cb); + if (predicate != null) { + cq.where(predicate); + } } - @Transactional - public Inject tryInject(String injectId) { - Inject inject = injectRepository.findById(injectId).orElseThrow(); - - // Reset injects outcome, communications and expectations - inject.clean(); - inject.setUpdatedAt(Instant.now()); - - // New inject status - InjectStatus injectStatus = new InjectStatus(); - injectStatus.setInject(inject); - injectStatus.setTrackingSentDate(Instant.now()); - injectStatus.setName(ExecutionStatus.QUEUING); - this.injectStatusRepository.save(injectStatus); - - // Return inject - return this.injectRepository.save(inject); + // -- Sorting -- + List orders = toSortCriteriaBuilder(cb, injectRoot, pageable.getSort()); + cq.orderBy(orders); + + // Type Query + TypedQuery query = entityManager.createQuery(cq); + + // -- Pagination -- + query.setFirstResult((int) pageable.getOffset()); + query.setMaxResults(pageable.getPageSize()); + + // -- EXECUTION -- + List injects = execAtomicTesting(query); + + Map> teamIds = new HashMap<>(); + Map> assetIds = new HashMap<>(); + Map> assetGroupIds = new HashMap<>(); + Map> injectExpectationIds = new HashMap<>(); + for (AtomicTestingOutput inject : injects) { + teamIds.putIfAbsent(inject.getId(), inject.getTeams()); + assetIds.putIfAbsent(inject.getId(), inject.getAssets()); + assetGroupIds.putIfAbsent(inject.getId(), inject.getAssetGroups()); + injectExpectationIds.putIfAbsent(inject.getId(), inject.getExpectations()); } - @Transactional - public void deleteAtomicTesting(String injectId) { - injectDocumentRepository.deleteDocumentsFromInject(injectId); - injectRepository.deleteById(injectId); + List teams = this.teamRepository.rawTeamByIds( + teamIds.values().stream().flatMap(Collection::stream).distinct().toList() + ); + List assets = this.assetRepository.rawByIds( + assetIds.values().stream().flatMap(Collection::stream).distinct().toList() + ); + List assetGroups = this.assetGroupRepository.rawAssetGroupByIds( + assetGroupIds.values().stream().flatMap(Collection::stream).distinct().toList() + ); + List expectations = this.injectExpectationRepository.rawByIds( + injectExpectationIds.values().stream().flatMap(Collection::stream).distinct().toList()); + + for (AtomicTestingOutput inject : injects) { + List currentTeamIds = teamIds.get(inject.getId()); + List currentAssetIds = assetIds.get(inject.getId()); + List currentAssetGroupIds = assetGroupIds.get(inject.getId()); + List currentInjectExpectationIds = injectExpectationIds.get(inject.getId()); + inject.setTargets(getTargetsFromRaw( + teams.stream().filter(t -> currentTeamIds.contains(t.getTeam_id())).toList(), + assets.stream().filter(a -> currentAssetIds.contains(a.getAsset_id())).toList(), + assetGroups.stream().filter(ag -> currentAssetGroupIds.contains(ag.getAsset_group_id())).toList() + )); + inject.setExpectationResultByTypes( + getRawExpectationResultByTypes( + expectations.stream().filter(e -> currentInjectExpectationIds.contains(e.getInject_expectation_id())) + .toList() + ) + ); } + + // -- Count Query -- + Long total = countQuery(cb, this.entityManager); + + return new PageImpl<>(injects, pageable, total); + } + + private void selectForAtomicTesting(CriteriaBuilder cb, CriteriaQuery cq, Root injectRoot) { + // Joins + Join injectorContractJoin = createLeftJoin(injectRoot, "injectorContract"); + Join injectorJoin = injectorContractJoin.join("injector", JoinType.LEFT); + Join injectStatusJoin = createLeftJoin(injectRoot, "status"); + // Array aggregations + Expression injectExpectationIdsExpression = createJoinArrayAggOnId(cb, injectRoot, "expectations"); + Expression teamIdsExpression = createJoinArrayAggOnId(cb, injectRoot, "teams"); + Expression assetIdsExpression = createJoinArrayAggOnId(cb, injectRoot, "assets"); + Expression assetGroupIdsExpression = createJoinArrayAggOnId(cb, injectRoot, "assetGroups"); + + // SELECT + cq.multiselect( + injectRoot.get("id").alias("inject_id"), + injectRoot.get("title").alias("inject_title"), + injectRoot.get("updatedAt").alias("inject_updated_at"), + injectorJoin.get("type").alias("inject_type"), + injectorContractJoin.alias("inject_injector_contract"), + injectStatusJoin.alias("inject_status"), + injectExpectationIdsExpression.alias("inject_expectations"), + teamIdsExpression.alias("inject_teams"), + assetIdsExpression.alias("inject_assets"), + assetGroupIdsExpression.alias("inject_asset_groups") + ).distinct(true); + + // GROUP BY + cq.groupBy(Arrays.asList( + injectRoot.get("id"), + injectorContractJoin.get("id"), + injectorJoin.get("id"), + injectStatusJoin.get("id") + )); + } + + private List execAtomicTesting(TypedQuery query) { + return query.getResultList() + .stream() + .map(tuple -> new AtomicTestingOutput( + tuple.get("inject_id", String.class), + tuple.get("inject_title", String.class), + tuple.get("inject_updated_at", Instant.class), + tuple.get("inject_type", String.class), + tuple.get("inject_injector_contract", InjectorContract.class), + tuple.get("inject_status", InjectStatus.class), + tuple.get("inject_expectations", String[].class), + tuple.get("inject_teams", String[].class), + tuple.get("inject_assets", String[].class), + tuple.get("inject_asset_groups", String[].class) + )) + .toList(); + } + } diff --git a/openbas-api/src/main/java/io/openbas/service/InjectService.java b/openbas-api/src/main/java/io/openbas/service/InjectService.java index b46bd158b1..3fdaf81b05 100644 --- a/openbas-api/src/main/java/io/openbas/service/InjectService.java +++ b/openbas-api/src/main/java/io/openbas/service/InjectService.java @@ -22,7 +22,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static io.openbas.utils.JpaUtils.arrayAgg; +import static io.openbas.utils.JpaUtils.createJoinArrayAggOnId; +import static io.openbas.utils.JpaUtils.createLeftJoin; import static java.time.Instant.now; @RequiredArgsConstructor @@ -80,19 +81,41 @@ public Inject updateInjectStatus(String injectId, InjectUpdateStatusInput input) public List injects(Specification specification) { CriteriaBuilder cb = this.entityManager.getCriteriaBuilder(); - // -- Create Query -- CriteriaQuery cq = cb.createTupleQuery(); Root injectRoot = cq.from(Inject.class); + selectForInject(cb, cq, injectRoot); + // -- Text Search and Filters -- + if (specification != null) { + Predicate predicate = specification.toPredicate(injectRoot, cq, cb); + if (predicate != null) { + cq.where(predicate); + } + } + + // -- Sorting -- + cq.orderBy(cb.asc(injectRoot.get("dependsDuration"))); + + // Type Query + TypedQuery query = this.entityManager.createQuery(cq); + + // -- EXECUTION -- + return execInject(query); + } + + // -- CRITERIA BUILDER -- + + private void selectForInject(CriteriaBuilder cb, CriteriaQuery cq, Root injectRoot) { // Joins Join injectExerciseJoin = createLeftJoin(injectRoot, "exercise"); Join injectScenarioJoin = createLeftJoin(injectRoot, "scenario"); Join injectorContractJoin = createLeftJoin(injectRoot, "injectorContract"); Join injectorJoin = injectorContractJoin.join("injector", JoinType.LEFT); - Expression tagIdsExpression = createArrayAgg(cb, injectRoot, "tags"); - Expression teamIdsExpression = createArrayAgg(cb, injectRoot, "teams"); - Expression assetIdsExpression = createArrayAgg(cb, injectRoot, "assets"); - Expression assetGroupIdsExpression = createArrayAgg(cb, injectRoot, "assetGroups"); + // Array aggregations + Expression tagIdsExpression = createJoinArrayAggOnId(cb, injectRoot, "tags"); + Expression teamIdsExpression = createJoinArrayAggOnId(cb, injectRoot, "teams"); + Expression assetIdsExpression = createJoinArrayAggOnId(cb, injectRoot, "assets"); + Expression assetGroupIdsExpression = createJoinArrayAggOnId(cb, injectRoot, "assetGroups"); // SELECT cq.multiselect( @@ -112,30 +135,17 @@ public List injects(Specification specification) { injectorJoin.get("type").alias("inject_type") ).distinct(true); - // Group By + // GROUP BY cq.groupBy(Arrays.asList( injectRoot.get("id"), - injectorContractJoin.get("id"), - injectorJoin.get("id"), injectExerciseJoin.get("id"), - injectScenarioJoin.get("id") + injectScenarioJoin.get("id"), + injectorContractJoin.get("id"), + injectorJoin.get("id") )); + } - // Sort - cq.orderBy(cb.asc(injectRoot.get("dependsDuration"))); - - // -- Specification -- - if (specification != null) { - Predicate predicate = specification.toPredicate(injectRoot, cq, cb); - if (predicate != null) { - cq.where(predicate); - } - } - - // Type Query - TypedQuery query = this.entityManager.createQuery(cq); - - // -- EXECUTION -- + private List execInject(TypedQuery query) { return query.getResultList() .stream() .map(tuple -> new InjectOutput( @@ -157,15 +167,6 @@ public List injects(Specification specification) { .toList(); } - private Join createLeftJoin(Root root, String attributeName) { - return root.join(attributeName, JoinType.LEFT); - } - - private Expression createArrayAgg(CriteriaBuilder cb, Root root, String attributeName) { - Join join = root.join(attributeName, JoinType.LEFT); - return arrayAgg(cb, join); - } - // -- TEST -- /** diff --git a/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java b/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java index 82f1d92c1a..f52d85eedb 100644 --- a/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java +++ b/openbas-api/src/main/java/io/openbas/utils/AtomicTestingUtils.java @@ -3,7 +3,10 @@ import io.openbas.atomic_testing.TargetType; import io.openbas.database.model.*; import io.openbas.database.model.InjectExpectation.EXPECTATION_TYPE; +import io.openbas.database.raw.RawAsset; +import io.openbas.database.raw.RawAssetGroup; import io.openbas.database.raw.RawInjectExpectation; +import io.openbas.database.raw.RawTeam; import io.openbas.expectation.ExpectationType; import io.openbas.rest.atomic_testing.form.InjectTargetWithResult; import io.openbas.utils.AtomicTestingMapper.ExpectationResultsByType; @@ -37,6 +40,27 @@ public static List getTargets( return targets; } + public static List getTargetsFromRaw( + final List teams, + final List assets, + final List assetGroups) { + List targets = new ArrayList<>(); + targets.addAll(teams + .stream() + .map(t -> new InjectTargetWithResult(TargetType.TEAMS, t.getTeam_id(), t.getTeam_name(), List.of(), null)) + .toList()); + targets.addAll(assets + .stream() + .map(t -> new InjectTargetWithResult(TargetType.ASSETS, t.getAsset_id(), t.getAsset_name(), List.of(), Objects.equals(t.getAsset_type(), "Endpoint") ? Endpoint.PLATFORM_TYPE.valueOf(t.getEndpoint_platform()): null)) + .toList()); + targets.addAll(assetGroups + .stream() + .map(t -> new InjectTargetWithResult(TargetType.ASSETS_GROUPS, t.getAsset_group_id(), t.getAsset_group_name(), List.of(), null)) + .toList()); + + return targets; + } + public static List getTargetsWithResults(final Inject inject) { List defaultExpectationResultsByTypes = getDefaultExpectationResultsByTypes(); List expectations = inject.getExpectations(); @@ -251,9 +275,9 @@ public static List getRawExpectationResultByTypes(fina List resultAvgOfExpectations = new ArrayList<>(); - getExpectationByType(ExpectationType.PREVENTION, preventionScores).map(resultAvgOfExpectations::add); - getExpectationByType(ExpectationType.DETECTION, detectionScores).map(resultAvgOfExpectations::add); - getExpectationByType(ExpectationType.HUMAN_RESPONSE, humanScores).map(resultAvgOfExpectations::add); + getExpectationByType(ExpectationType.PREVENTION, preventionScores).ifPresent(resultAvgOfExpectations::add); + getExpectationByType(ExpectationType.DETECTION, detectionScores).ifPresent(resultAvgOfExpectations::add); + getExpectationByType(ExpectationType.HUMAN_RESPONSE, humanScores).ifPresent(resultAvgOfExpectations::add); return resultAvgOfExpectations; } diff --git a/openbas-framework/src/main/java/io/openbas/utils/JpaUtils.java b/openbas-framework/src/main/java/io/openbas/utils/JpaUtils.java index 4d886567b0..84efaef6bb 100644 --- a/openbas-framework/src/main/java/io/openbas/utils/JpaUtils.java +++ b/openbas-framework/src/main/java/io/openbas/utils/JpaUtils.java @@ -10,6 +10,10 @@ public class JpaUtils { + private JpaUtils() { + + } + public static Expression toPath( @NotNull final PropertySchema propertySchema, @NotNull final Root root, @@ -29,17 +33,6 @@ else if (propertySchema.isFilterable() && hasText(propertySchema.getPropertyRepr } } - public static Expression arrayAgg( - @NotNull final CriteriaBuilder cb, - @NotNull final Join join) { - return cb.function( - "array_remove", - String[].class, - cb.function("array_agg", String[].class, join.get("id")), - cb.nullLiteral(String.class) - ); - } - // -- FUNCTION -- public static Expression arrayAggOnId( diff --git a/openbas-model/src/main/java/io/openbas/database/criteria/InjectCriteria.java b/openbas-model/src/main/java/io/openbas/database/criteria/InjectCriteria.java new file mode 100644 index 0000000000..422a8503bf --- /dev/null +++ b/openbas-model/src/main/java/io/openbas/database/criteria/InjectCriteria.java @@ -0,0 +1,25 @@ +package io.openbas.database.criteria; + +import io.openbas.database.model.Inject; +import jakarta.persistence.EntityManager; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import org.jetbrains.annotations.NotNull; + +public class InjectCriteria { + + private InjectCriteria() { + + } + + public static Long countQuery( + @NotNull final CriteriaBuilder cb, + @NotNull final EntityManager entityManager) { + CriteriaQuery countQuery = cb.createQuery(Long.class); + Root countRoot = countQuery.from(Inject.class); + countQuery.select(cb.count(countRoot)); + return entityManager.createQuery(countQuery).getSingleResult(); + } + +} diff --git a/openbas-model/src/main/java/io/openbas/database/model/Inject.java b/openbas-model/src/main/java/io/openbas/database/model/Inject.java index 42baa07542..bfe9c86546 100644 --- a/openbas-model/src/main/java/io/openbas/database/model/Inject.java +++ b/openbas-model/src/main/java/io/openbas/database/model/Inject.java @@ -54,6 +54,7 @@ public class Inject implements Base, Injection { @Queryable(searchable = true, filterable = true, sortable = true) @Column(name = "inject_title") @JsonProperty("inject_title") + @NotBlank private String title; @Getter @@ -85,12 +86,14 @@ public class Inject implements Base, Injection { @Getter @Column(name = "inject_created_at") @JsonProperty("inject_created_at") + @NotNull private Instant createdAt = now(); @Getter @Column(name = "inject_updated_at") @Queryable(sortable = true) @JsonProperty("inject_updated_at") + @NotNull private Instant updatedAt = now(); @Getter @@ -254,9 +257,9 @@ public boolean isReady() { getInjectorContract(), getContent(), isAllTeams(), - getTeams().stream().map(Team::getId).collect(Collectors.toSet()), - getAssets().stream().map(Asset::getId).collect(Collectors.toSet()), - getAssetGroups().stream().map(AssetGroup::getId).collect(Collectors.toSet()) + getTeams().stream().map(Team::getId).collect(Collectors.toList()), + getAssets().stream().map(Asset::getId).collect(Collectors.toList()), + getAssetGroups().stream().map(AssetGroup::getId).collect(Collectors.toList()) ); } diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawAsset.java b/openbas-model/src/main/java/io/openbas/database/raw/RawAsset.java index cad3aae31c..b529e7abd1 100644 --- a/openbas-model/src/main/java/io/openbas/database/raw/RawAsset.java +++ b/openbas-model/src/main/java/io/openbas/database/raw/RawAsset.java @@ -2,12 +2,12 @@ public interface RawAsset { - public String getAsset_id(); + String getAsset_id(); - public String getAsset_type(); + String getAsset_type(); - public String getAsset_name(); + String getAsset_name(); - public String getEndpoint_platform(); + String getEndpoint_platform(); } diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawTeam.java b/openbas-model/src/main/java/io/openbas/database/raw/RawTeam.java index eb8b083151..f5492a750f 100644 --- a/openbas-model/src/main/java/io/openbas/database/raw/RawTeam.java +++ b/openbas-model/src/main/java/io/openbas/database/raw/RawTeam.java @@ -5,31 +5,31 @@ public interface RawTeam { - public String getTeam_id(); + String getTeam_id(); - public String getTeam_name(); + String getTeam_name(); - public String getTeam_description(); + String getTeam_description(); - public Instant getTeam_created_at(); + Instant getTeam_created_at(); - public Instant getTeam_updated_at(); + Instant getTeam_updated_at(); - public String getTeam_organization(); + String getTeam_organization(); - public boolean getTeam_contextual(); + boolean getTeam_contextual(); - public Set getTeam_tags(); + Set getTeam_tags(); - public Set getTeam_users(); + Set getTeam_users(); - public Set getTeam_exercises(); + Set getTeam_exercises(); - public Set getTeam_scenarios(); + Set getTeam_scenarios(); - public Set getTeam_expectations(); + Set getTeam_expectations(); - public Set getTeam_exercise_injects(); + Set getTeam_exercise_injects(); - public Set getTeam_communications(); + Set getTeam_communications(); } diff --git a/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java index b3f34e636d..0610158485 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/InjectExpectationRepository.java @@ -67,11 +67,11 @@ List findAllByInjectAndAssetGroup( @Param("assetGroupId") @NotBlank final String assetGroupId ); - @Query(value = "SELECT team_id, asset_id, asset_group_id, inject_expectation_type, " + - "inject_expectation_score, inject_expectation_id, exercise_id, inject_expectation_expected_score " + - "FROM injects_expectations i where i.inject_expectation_id IN :ids", - nativeQuery = true) - List rawByIds( - @Param("ids") final List ids - ); + @Query(value = "SELECT " + + "team_id, asset_id, asset_group_id, inject_expectation_type, " + + "inject_expectation_score, inject_expectation_id, exercise_id, inject_expectation_expected_score " + + "FROM injects_expectations i " + + "where i.inject_expectation_id IN :ids", + nativeQuery = true) + List rawByIds(@Param("ids") final List ids); } diff --git a/openbas-model/src/main/java/io/openbas/helper/InjectModelHelper.java b/openbas-model/src/main/java/io/openbas/helper/InjectModelHelper.java index 54938baf0d..cc2646f8d1 100644 --- a/openbas-model/src/main/java/io/openbas/helper/InjectModelHelper.java +++ b/openbas-model/src/main/java/io/openbas/helper/InjectModelHelper.java @@ -10,7 +10,6 @@ import java.time.ZoneOffset; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.StreamSupport; @@ -28,9 +27,9 @@ public static boolean isReady( InjectorContract injectorContract, ObjectNode content, boolean allTeams, - @NotNull final Set teams, - @NotNull final Set assets, - @NotNull final Set assetGroups) { + @NotNull final List teams, + @NotNull final List assets, + @NotNull final List assetGroups) { if (injectorContract == null) { return false; }