Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f8a2451
Update parent POM to version `7.0.0-RC10.6` and configure migration h…
renczesstefan Jul 15, 2026
780c506
Release 7.0.0-rc10.5
machacjozef Jul 16, 2026
131e77e
Update `ProcessRoleRepository` with `findByNetworkIdentifierAndObject…
renczesstefan Jul 23, 2026
149671c
Introduce asynchronous execution support in `ActionDelegate` with sta…
renczesstefan Jul 27, 2026
cf8ec2d
Introduce asynchronous execution support in `ActionDelegate` with sta…
renczesstefan Jul 27, 2026
b3a0def
[NAE-2417] Fix dependency vulnerabilities
machacjozef Jul 27, 2026
16c3d08
Remove `MigrationMongoTemplateConfiguration` and associated fallback …
renczesstefan Jul 27, 2026
0ad54dd
Merge branch 'release/0.10.5' into NAE-2464
renczesstefan Jul 27, 2026
aa359a8
Refactor imports and update test dependencies in `ActionDelegateTest`…
renczesstefan Jul 27, 2026
436032a
Refactor repository methods to use `findByNetworkIdentifierAndObjectI…
renczesstefan Jul 28, 2026
6256f71
Refactor `ElasticCaseService` to improve full-text search logic and v…
renczesstefan Jul 29, 2026
56148a2
Update key field in `CaseEventHandler` and simplify regex in `Elastic…
renczesstefan Jul 29, 2026
fac8492
Refactor `ElasticCaseService` full-text search logic: replace `QueryS…
renczesstefan Jul 29, 2026
a4beb5d
Refactor `GroovyShellFactory` to improve import handling logic: repla…
renczesstefan Jul 29, 2026
96c7c17
Merge pull request #467 from netgrif/NAE-2464_2
machacjozef Jul 30, 2026
8353e17
Refactor `GroovyShellFactory` to improve import handling logic: repla…
renczesstefan Jul 30, 2026
359567f
Merge remote-tracking branch 'origin/NAE-2464' into NAE-2464
renczesstefan Jul 30, 2026
56fbc83
Release 7.0.2
machacjozef Jul 31, 2026
1a816b2
Reuse the default task executor for asynchronous action execution
renczesstefan Aug 3, 2026
503ab24
Refactor `ElasticCaseService`: replace `replace` with `replaceAll` fo…
renczesstefan Aug 3, 2026
c9928f1
Add unit and integration tests for `ElasticCaseService` to validate f…
renczesstefan Aug 3, 2026
90f9147
Refactor `ElasticCaseService`: replace `replaceAll` with `replace` fo…
renczesstefan Aug 3, 2026
8f0ae29
Deprecate and update case repository methods, add detailed documentat…
renczesstefan Aug 3, 2026
702824c
Refactor `ElasticCaseService`: use `Matcher.quoteReplacement` for saf…
renczesstefan Aug 3, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

FROM eclipse-temurin:21-jre

ARG VERSION="7.0.1"
ARG VERSION="7.0.2"

LABEL authors="Netgrif <devops@netgrif.com>" \
org.opencontainers.image.authors="NETGRIF <devops@netgrif.com>" \
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.multi-stage
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ RUN mvn -B -e -DskipTests -P docker-build clean install
# prepare runtime
FROM eclipse-temurin:21-jre-jammy

ARG VERSION="7.0.1"
ARG VERSION="7.0.2"

LABEL authors="Netgrif <devops@netgrif.com>" \
org.opencontainers.image.authors="NETGRIF <devops@netgrif.com>" \
Expand Down
2 changes: 1 addition & 1 deletion application-engine/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>com.netgrif</groupId>
<artifactId>application-engine-parent</artifactId>
<version>7.0.1</version>
<version>7.0.2</version>
</parent>

<artifactId>application-engine</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,73 @@
package com.netgrif.application.engine

import org.springframework.scheduling.annotation.Async
import com.netgrif.application.engine.petrinet.domain.dataset.logic.action.ActionDelegate
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.context.annotation.Bean
import org.springframework.core.task.TaskExecutor
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
import org.springframework.stereotype.Service

import java.util.concurrent.atomic.AtomicBoolean

@Service
class AsyncRunner {

@Async
private final TaskExecutor actionsExecutor

AsyncRunner(@Qualifier("taskExecutor") TaskExecutor actionsExecutor) {
this.actionsExecutor = actionsExecutor
}

void run(Closure closure) {
closure()
ActionDelegate actionDelegate = findActionDelegate(closure)
actionDelegate?.retainForAsyncExecution()
AtomicBoolean released = new AtomicBoolean()

Runnable task = {
try {
closure()
} finally {
release(actionDelegate, released)
}
} as Runnable

try {
execute(task)
} catch (Throwable throwable) {
release(actionDelegate, released)
throw throwable
}
}

@Async
void execute(final Runnable runnable) {
runnable.run()
actionsExecutor.execute(runnable)
}

private static void release(ActionDelegate actionDelegate, AtomicBoolean released) {
if (actionDelegate != null && released.compareAndSet(false, true)) {
actionDelegate.releaseAfterAsyncExecution()
}
}

private static ActionDelegate findActionDelegate(Closure closure) {
Set<Object> visited = Collections.newSetFromMap(new IdentityHashMap<>())
return findActionDelegate(closure, visited)
}

private static ActionDelegate findActionDelegate(Object candidate, Set<Object> visited) {
if (candidate == null || !visited.add(candidate)) {
return null
}
if (candidate instanceof ActionDelegate) {
return candidate
}
if (!(candidate instanceof Closure)) {
return null
}

Closure nestedClosure = (Closure) candidate
return findActionDelegate(nestedClosure.delegate, visited)
?: findActionDelegate(nestedClosure.owner, visited)
?: findActionDelegate(nestedClosure.thisObject, visited)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetServi
import com.querydsl.core.types.Predicate
import groovy.util.logging.Slf4j
import org.bson.types.ObjectId
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.data.mongodb.core.BulkOperations
import org.springframework.data.mongodb.core.FindAndReplaceOptions
import org.springframework.data.mongodb.core.MongoTemplate
Expand Down Expand Up @@ -65,7 +66,7 @@ class CaseMigrationHelper extends AbstractMigrationHelper<Case> {
* @param mongoTemplate MongoTemplate to interact with MongoDB.
* @param migrationConfigurationProperties Properties for migration configuration, including cases.
*/
CaseMigrationHelper(MongoTemplate mongoTemplate,
CaseMigrationHelper(@Qualifier("mongoTemplate") MongoTemplate mongoTemplate,
MigrationProperties migrationProperties,
IPetriNetService petriNetService,
IElasticCaseService elasticCaseService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetServi
import groovy.util.logging.Slf4j
import org.apache.tomcat.util.http.fileupload.IOUtils
import org.springframework.beans.factory.ObjectFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.core.io.ClassPathResource
import org.springframework.core.io.Resource
import org.springframework.data.domain.Pageable
Expand Down Expand Up @@ -83,7 +84,7 @@ class PetriNetMigrationHelper extends AbstractMigrationHelper<PetriNet> {
* @param importerProvider the {@link ObjectFactory} that supplies {@link Importer} instances for importing Petri Net models from various sources
* @param userService the {@link UserService} for managing user-related operations, including retrieving system user for Petri Net imports
*/
PetriNetMigrationHelper(MongoTemplate mongoTemplate,
PetriNetMigrationHelper(@Qualifier("mongoTemplate") MongoTemplate mongoTemplate,
MigrationProperties migrationProperties,
IPetriNetService petriNetService,
ProcessRoleRepository processRoleRepository,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.netgrif.application.engine.petrinet.service.interfaces.IPetriNetServi
import com.netgrif.application.engine.workflow.service.interfaces.ITaskService
import com.querydsl.core.types.Predicate
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.data.mongodb.core.BulkOperations
import org.springframework.data.mongodb.core.MongoTemplate
import org.springframework.data.mongodb.core.query.Criteria
Expand Down Expand Up @@ -70,7 +71,7 @@ class TaskMigrationHelper extends AbstractMigrationHelper<Task> {
*
* @param mongoTemplate the {@link MongoTemplate} to use for interacting with MongoDB
*/
TaskMigrationHelper(MongoTemplate mongoTemplate,
TaskMigrationHelper(@Qualifier("mongoTemplate") MongoTemplate mongoTemplate,
MigrationProperties migrationProperties,
IPetriNetService petriNetService,
ITaskService taskService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ class ActionDelegate extends DelegateExpando {
FieldActionsRunner actionsRunner
List<EventOutcome> outcomes

private int pendingAsyncExecutions
private boolean executionFinished
private boolean executionStateCleared

def init(Action action, Case useCase, Optional<Task> task, FieldActionsRunner actionsRunner, Map<String, String> params = [:]) {
this.action = action
this.useCase = useCase
Expand All @@ -254,7 +258,32 @@ class ActionDelegate extends DelegateExpando {
this.Plugin = new PluginHolder()
}

void clearAfterExecution() {
synchronized void retainForAsyncExecution() {
if (executionStateCleared) {
throw new IllegalStateException("Action execution state has already been cleared")
}
pendingAsyncExecutions++
}

synchronized void releaseAfterAsyncExecution() {
if (pendingAsyncExecutions == 0) {
throw new IllegalStateException("No asynchronous action execution is pending")
}
pendingAsyncExecutions--
clearExecutionStateIfPossible()
}

synchronized void clearAfterExecution() {
executionFinished = true
clearExecutionStateIfPossible()
}

private void clearExecutionStateIfPossible() {
if (!executionFinished || pendingAsyncExecutions != 0 || executionStateCleared) {
return
}
executionStateCleared = true

this.action = null
this.useCase = null
this.task = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import com.mongodb.connection.*;
import com.netgrif.application.engine.configuration.properties.DataConfigurationProperties;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.*;

import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ protected String[] getDefaultEngineImports() {
return new String[]{
"com.netgrif.application.engine.objects",
"com.netgrif.application.engine.adapter.spring",
"java.time"
"com.netgrif.application.engine.objects.petrinet.domain.dataset",
"org.bson.types",
"java.time",
"java.util",
"java.util.stream"
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "nae.migration")
@ConfigurationProperties(prefix = "netgrif.engine.migration")
public class MigrationProperties {

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import com.netgrif.application.engine.configuration.properties.DataConfigurationProperties;
import com.netgrif.application.engine.elastic.domain.BulkOperationWrapper;
import com.netgrif.application.engine.elastic.service.model.FullTextField;
import com.netgrif.application.engine.objects.auth.domain.LoggedUser;
import com.netgrif.application.engine.objects.elastic.domain.ElasticCase;
import com.netgrif.application.engine.elastic.domain.ElasticCaseRepository;
Expand Down Expand Up @@ -42,6 +43,7 @@

import java.util.*;
import java.util.function.BinaryOperator;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -59,7 +61,7 @@
protected DataConfigurationProperties.ElasticsearchProperties elasticProperties;
protected IPetriNetService petriNetService;
protected IWorkflowService workflowService;
protected IElasticCasePrioritySearch iElasticCasePrioritySearch;
protected IElasticCasePrioritySearch elasticCasePrioritySearch;
protected ApplicationEventPublisher publisher;
protected ElasticQueueManager caseElasticIndexQueueManager;
protected ElasticQueueManager caseElasticDeleteQueueManager;
Expand All @@ -70,7 +72,7 @@
DataConfigurationProperties.ElasticsearchProperties elasticProperties,
@Lazy IPetriNetService petriNetService,
@Lazy IWorkflowService workflowService,
IElasticCasePrioritySearch iElasticCasePrioritySearch,
IElasticCasePrioritySearch elasticCasePrioritySearch,
ApplicationEventPublisher publisher,
ElasticsearchClient elasticsearchClient) {
this.repository = repository;
Expand All @@ -79,7 +81,7 @@
this.elasticProperties = elasticProperties;
this.petriNetService = petriNetService;
this.workflowService = workflowService;
this.iElasticCasePrioritySearch = iElasticCasePrioritySearch;
this.elasticCasePrioritySearch = elasticCasePrioritySearch;
this.publisher = publisher;
this.caseElasticIndexQueueManager = new ElasticQueueManager(elasticProperties, elasticsearchClient, publisher);
this.caseElasticDeleteQueueManager = new ElasticQueueManager(elasticProperties, elasticsearchClient, publisher);
Expand Down Expand Up @@ -413,15 +415,37 @@
}

protected void buildFullTextQuery(CaseSearchRequest request, BoolQuery.Builder query) {
if (request.fullText == null || request.fullText.isEmpty()) {
if (request.fullText == null || request.fullText.isBlank()) {
return;
}

// TODO: improvement? wildcard does not scale good
//String searchText = elasticsearchProperties.isAnalyzerEnabled() ? request.fullText : "*" + request.fullText + "*";
String searchText = "*" + request.fullText + "*";
QueryStringQuery fullTextQuery = QueryStringQuery.of(builder -> builder.fields(iElasticCasePrioritySearch.fullTextFields()).query(searchText));
query.must(fullTextQuery._toQuery());
List<String> fullTextTerms = normalizeFullTextSearch(request.fullText);
if (fullTextTerms.isEmpty()) {
return;
}

List<FullTextField> fullTextFields = elasticCasePrioritySearch.fullTextFields().stream()
.map(this::parseFullTextField)
.toList();

BoolQuery.Builder fullTextQuery = new BoolQuery.Builder();

fullTextTerms.forEach(term -> {
BoolQuery.Builder termQuery = new BoolQuery.Builder();
String wildcardValue = "*" + escapeWildcardValue(term) + "*";

fullTextFields.forEach(fullTextField -> termQuery.should(QueryBuilders.wildcard(builder -> builder
.field(fullTextField.field())
.value(wildcardValue)
.caseInsensitive(true)
.boost(fullTextField.boost())
)));

termQuery.minimumShouldMatch("1");
fullTextQuery.must(termQuery.build()._toQuery());
});

query.must(fullTextQuery.build()._toQuery());
}

/**
Expand Down Expand Up @@ -531,4 +555,44 @@
.id(useCase.getId())
.document(template.getElasticsearchConverter().mapObject(useCase))));
}

private List<String> normalizeFullTextSearch(String fullText) {
return Arrays.stream(Matcher.quoteReplacement(fullText)
.replace("\\\\", "")
.replaceAll("\\s+", " ")
.trim()
.split("\\s+"))
.map(String::trim)
.map(this::removeDanglingEscapeCharacters)
.filter(term -> !term.isBlank())
.toList();
}

private String removeDanglingEscapeCharacters(String term) {
return term.replaceAll("\\\\+$", "");

Check warning on line 572 in application-engine/src/main/java/com/netgrif/application/engine/elastic/service/ElasticCaseService.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Simplify this regular expression to reduce its runtime, as it has super-linear performance due to backtracking.

See more on https://sonarcloud.io/project/issues?id=netgrif_application-engine&issues=AZ_HfTVgcT0A44-HwmgA&open=AZ_HfTVgcT0A44-HwmgA&pullRequest=464
}

private FullTextField parseFullTextField(String fieldDefinition) {
String[] parts = fieldDefinition.split("\\^", 2);
String field = parts[0].trim();
float boost = 1.0f;

if (parts.length == 2 && !parts[1].isBlank()) {
try {
boost = Float.parseFloat(parts[1].trim());
boost = Float.isFinite(boost) && boost > 0 ? boost : 1.0f;
} catch (NumberFormatException e) {
log.warn("Invalid boost [{}] in fulltext field definition [{}]. Using default boost 1.0.", parts[1], fieldDefinition);
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

return new FullTextField(field, boost);
}

private String escapeWildcardValue(String value) {
return value
.replace("\\", "\\\\")
.replace("*", "\\*")
.replace("?", "\\?");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.netgrif.application.engine.elastic.service.model;

public record FullTextField(String field, float boost) {
}
Loading
Loading