All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netgrif.application.engine.elastic.service.ElasticTaskService Maven / Gradle / Ivy

Go to download

System provides workflow management functions including user, role and data management.

There is a newer version: 6.3.3
Show newest version
package com.netgrif.application.engine.elastic.service;

import com.google.common.collect.ImmutableMap;
import com.netgrif.application.engine.auth.domain.LoggedUser;
import com.netgrif.application.engine.elastic.domain.ElasticJob;
import com.netgrif.application.engine.elastic.domain.ElasticQueryConstants;
import com.netgrif.application.engine.elastic.domain.ElasticTask;
import com.netgrif.application.engine.elastic.domain.ElasticTaskJob;
import com.netgrif.application.engine.elastic.service.interfaces.IElasticTaskService;
import com.netgrif.application.engine.elastic.web.requestbodies.ElasticTaskSearchRequest;
import com.netgrif.application.engine.petrinet.domain.PetriNetSearch;
import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetService;
import com.netgrif.application.engine.petrinet.web.responsebodies.PetriNetReference;
import com.netgrif.application.engine.utils.FullPageRequest;
import com.netgrif.application.engine.workflow.domain.Task;
import com.netgrif.application.engine.workflow.service.interfaces.ITaskService;
import com.netgrif.application.engine.workflow.web.requestbodies.TaskSearchRequest;
import com.netgrif.application.engine.workflow.web.requestbodies.taskSearch.PetriNet;
import com.netgrif.application.engine.workflow.web.requestbodies.taskSearch.TaskSearchCaseRequest;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryStringQueryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.Future;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;

import static org.elasticsearch.index.query.QueryBuilders.*;

@Service
public class ElasticTaskService extends ElasticViewPermissionService implements IElasticTaskService {

    private static final Logger log = LoggerFactory.getLogger(ElasticTaskService.class);

    protected ITaskService taskService;
    protected ElasticsearchRestTemplate template;

    @Value("${spring.data.elasticsearch.index.task}")
    protected String taskIndex;

    @Autowired
    protected ElasticsearchRestTemplate elasticsearchTemplate;

    @Autowired
    protected IPetriNetService petriNetService;

    protected Map fullTextFieldMap = ImmutableMap.of(
            "title", 1f,
            "caseTitle", 1f
    );

    protected Map caseTitledMap = ImmutableMap.of(
            "caseTitle", 1f
    );

    @Autowired
    private ElasticTaskQueueManager elasticTaskQueueManager;

    @Autowired
    public ElasticTaskService(ElasticsearchRestTemplate template) {
        this.template = template;
    }

    @Autowired
    @Lazy
    public void setTaskService(ITaskService taskService) {
        this.taskService = taskService;
    }

    /**
     * See {@link QueryStringQueryBuilder#fields(Map)}
     *
     * @return map where keys are ElasticCase field names and values are boosts of these fields
     */
    @Override
    public Map fullTextFields() {
        return fullTextFieldMap;
    }

    @Override
    public void remove(String taskId) {
        ElasticTask task = new ElasticTask();
        task.setTaskId(taskId);
        elasticTaskQueueManager.scheduleOperation(new ElasticTaskJob(ElasticJob.REMOVE, task));
    }

    @Override
    public void removeByPetriNetId(String petriNetId) {
        elasticTaskQueueManager.removeTasksByProcess(petriNetId);
    }

    @Override
    public Future scheduleTaskIndexing(ElasticTask task) {
        return elasticTaskQueueManager.scheduleOperation(new ElasticTaskJob(ElasticJob.INDEX, task));
    }

    @Async
    @Override
    public void index(ElasticTask task) {
        elasticTaskQueueManager.scheduleOperation(new ElasticTaskJob(ElasticJob.INDEX, task));
    }

    @Override
    public void indexNow(ElasticTask task) {
        index(task);
    }

    @Override
    public Page search(List requests, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection) {
        NativeSearchQuery query = buildQuery(requests, user.getSelfOrImpersonated(), pageable, locale, isIntersection);
        List taskPage;
        long total;
        if (query != null) {
            SearchHits hits = elasticsearchTemplate.search(query, ElasticTask.class, IndexCoordinates.of(taskIndex));
            Page indexedTasks = (Page) SearchHitSupport.unwrapSearchHits(SearchHitSupport.searchPageFor(hits, query.getPageable()));
            taskPage = taskService.findAllById(indexedTasks.get().map(ElasticTask::getStringId).collect(Collectors.toList()));
            total = indexedTasks.getTotalElements();
        } else {
            taskPage = Collections.emptyList();
            total = 0;
        }

        return new PageImpl<>(taskPage, pageable, total);
    }

    @Override
    public long count(List requests, LoggedUser user, Locale locale, Boolean isIntersection) {
        NativeSearchQuery query = buildQuery(requests, user.getSelfOrImpersonated(), new FullPageRequest(), locale, isIntersection);
        if (query != null) {
            return template.count(query, ElasticTask.class);
        } else {
            return 0;
        }
    }

    protected NativeSearchQuery buildQuery(List requests, LoggedUser user, Pageable pageable, Locale locale, Boolean isIntersection) {
        List singleQueries = requests.stream().map(request -> buildSingleQuery(request, user, locale)).collect(Collectors.toList());

        if (isIntersection && !singleQueries.stream().allMatch(Objects::nonNull)) {
            // one of the queries evaluates to empty set => the entire result is an empty set
            return null;
        } else if (!isIntersection) {
            singleQueries = singleQueries.stream().filter(Objects::nonNull).collect(Collectors.toList());
            if (singleQueries.size() == 0) {
                // all queries result in an empty set => the entire result is an empty set
                return null;
            }
        }

        BinaryOperator reductionOperator = isIntersection ? BoolQueryBuilder::must : BoolQueryBuilder::should;
        BoolQueryBuilder query = singleQueries.stream().reduce(new BoolQueryBuilder(), reductionOperator);

        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        return builder
                .withQuery(query)
                .withPageable(pageable)
                .build();
    }

    protected BoolQueryBuilder buildSingleQuery(ElasticTaskSearchRequest request, LoggedUser user, Locale locale) {
        if (request == null) {
            throw new IllegalArgumentException("Request can not be null!");
        }
        addRolesQueryConstraint(request, user);

        BoolQueryBuilder query = boolQuery();
        buildViewPermissionQuery(query, user);
        buildCaseQuery(request, query);
        buildTitleQuery(request, query);
        buildUserQuery(request, query);
        buildProcessQuery(request, query);
        buildFullTextQuery(request, query);
        buildTransitionQuery(request, query);
        buildTagsQuery(request, query);
        buildStringQuery(request, query, user);
        boolean resultAlwaysEmpty = buildGroupQuery(request, user, locale, query);

        if (resultAlwaysEmpty)
            return null;
        else
            return query;
    }

    protected void addRolesQueryConstraint(ElasticTaskSearchRequest request, LoggedUser user) {
        if (request.role != null && !request.role.isEmpty()) {
            Set roles = new HashSet<>(request.role);
            roles.addAll(user.getProcessRoles());
            request.role = new ArrayList<>(roles);
        } else {
            request.role = new ArrayList<>(user.getProcessRoles());
        }
    }


    /**
     * Tasks of case with id "5cb07b6ff05be15f0b972c4d"
     * {
     * "case": {
     * "id": "5cb07b6ff05be15f0b972c4d"
     * }
     * }
     * 

* Tasks of cases with id "5cb07b6ff05be15f0b972c4d" OR "5cb07b6ff05be15f0b972c4e" * { * "case": [{ * "id": "5cb07b6ff05be15f0b972c4d" * }, * { * "id": "5cb07b6ff05be15f0b972c4e" * }] * } *

* Tasks of case with case title containing "foo" * { * "case": { * "title": "foo" * } * } *

* Tasks of case with case title containing "foo" OR "bar" * { * "case": [{ * "title": "foo" * }, * { * "title: "bar" * }] * } */ protected void buildCaseQuery(ElasticTaskSearchRequest request, BoolQueryBuilder query) { if (request.useCase == null || request.useCase.isEmpty()) { return; } BoolQueryBuilder casesQuery = boolQuery(); request.useCase.stream().map(this::caseRequestQuery).filter(Objects::nonNull).forEach(casesQuery::should); query.filter(casesQuery); } /** * @return query for ID if only ID is present. Query for title if only title is present. * If both are present an ID query is returned. If neither are present null is returned. */ protected QueryBuilder caseRequestQuery(TaskSearchCaseRequest caseRequest) { if (caseRequest.id != null) { return termQuery("caseId", caseRequest.id); } else if (caseRequest.title != null) { return queryStringQuery("*" + caseRequest.title + "*").fields(this.caseTitledMap); } return null; } /** * Tasks with title (default value) "New task" * { * "title": "New task" * } *

* Tasks with title (default value) "New task" OR "Status" * { * "title": [ * "New task", * "Status" * ] * } */ protected void buildTitleQuery(ElasticTaskSearchRequest request, BoolQueryBuilder query) { if (request.title == null || request.title.isEmpty()) { return; } BoolQueryBuilder titleQuery = boolQuery(); for (String title : request.title) { titleQuery.should(termQuery("title", title)); } query.filter(titleQuery); } /** * Tasks assigned to user with id 1 * { * "user": 1 * } *

* Tasks assigned to user with id 1 OR 2 */ protected void buildUserQuery(ElasticTaskSearchRequest request, BoolQueryBuilder query) { if (request.user == null || request.user.isEmpty()) { return; } BoolQueryBuilder userQuery = boolQuery(); for (String user : request.user) { userQuery.should(termQuery("userId", user)); } query.filter(userQuery); } /** * Tasks of process "document" * { * "process": "document" * } *

* Tasks of process "document" OR "folder" * { * "process": [ * "document", * "folder", * ] * } */ protected void buildProcessQuery(ElasticTaskSearchRequest request, BoolQueryBuilder query) { if (request.process == null || request.process.isEmpty()) { return; } BoolQueryBuilder processQuery = boolQuery(); for (PetriNet process : request.process) { if (process.identifier != null) { processQuery.should(termQuery("processId", process.identifier)); } } query.filter(processQuery); } /** * Full text search on fields defined by {@link #fullTextFields()}. */ protected void buildFullTextQuery(ElasticTaskSearchRequest request, BoolQueryBuilder query) { if (request.fullText == null || request.fullText.isEmpty()) { return; } QueryBuilder fullTextQuery = queryStringQuery("*" + request.fullText + "*").fields(fullTextFields()); query.must(fullTextQuery); } /** * Tasks with transition id "document" * { * "transitionId": "document" * } *

* Tasks with transition id "document" OR "folder" * { * "transitionId": [ * "document", * "folder", * ] * } */ protected void buildTransitionQuery(ElasticTaskSearchRequest request, BoolQueryBuilder query) { if (request.transitionId == null || request.transitionId.isEmpty()) { return; } BoolQueryBuilder transitionQuery = boolQuery(); request.transitionId.forEach(transitionId -> transitionQuery.should(termQuery("transitionId", transitionId))); query.filter(transitionQuery); } private void buildTagsQuery(ElasticTaskSearchRequest request, BoolQueryBuilder query) { if (request.tags == null || request.tags.isEmpty()) { return; } BoolQueryBuilder tagsQuery = boolQuery(); for (Map.Entry field : request.tags.entrySet()) { tagsQuery.must(termQuery("tags." + field.getKey(), field.getValue())); } query.filter(tagsQuery); } /** * See Query String Query */ protected void buildStringQuery(ElasticTaskSearchRequest request, BoolQueryBuilder query, LoggedUser user) { if (request.query == null || request.query.isEmpty()) { return; } String populatedQuery = request.query.replaceAll(ElasticQueryConstants.USER_ID_TEMPLATE, user.getId().toString()); query.must(queryStringQuery(populatedQuery)); } /** * Tasks of cases of group with id "5cb07b6ff05be15f0b972c4d" * { * "group": "5cb07b6ff05be15f0b972c4d" * } *

* Tasks of cases of group with id "5cb07b6ff05be15f0b972c4d" OR "5cb07b6ff05be15f0b972c4e" * { * "transitionId": [ * "5cb07b6ff05be15f0b972c4d", * "5cb07b6ff05be15f0b972c4e", * ] * } */ public boolean buildGroupQuery(TaskSearchRequest request, LoggedUser user, Locale locale, BoolQueryBuilder query) { if (request.group == null || request.group.isEmpty()) return false; PetriNetSearch processQuery = new PetriNetSearch(); processQuery.setGroup(request.group); List groupProcesses = this.petriNetService.search(processQuery, user, new FullPageRequest(), locale).getContent(); if (groupProcesses.size() == 0) return true; BoolQueryBuilder groupProcessQuery = boolQuery(); for (PetriNetReference process : groupProcesses) { groupProcessQuery.should(termQuery("processId", process.getStringId())); } query.filter(groupProcessQuery); return false; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy