Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ class EnumerationMapField extends MapOptionsField<I18nString, String> {
super()
}

EnumerationMapField(Map<String, I18nString> choices) {
super(choices)
EnumerationMapField(Map<String, I18nString> options) {
super(options)
}

EnumerationMapField(Map<String, I18nString> choices, String defaultValue) {
super(choices)
EnumerationMapField(Map<String, I18nString> options, String defaultValue) {
super(options)
this.defaultValue = defaultValue
}

Expand Down Expand Up @@ -45,6 +45,22 @@ class EnumerationMapField extends MapOptionsField<I18nString, String> {
super.setDefaultValue(defaultValue)
}

/**
* Returns the internationalized string value corresponding to the currently selected option key.
* <p>
* This method retrieves the {@link I18nString} from the options map that corresponds to the
* current value of this field.
* </p>
*
* @return the {@link I18nString} object representing the internationalized value of the selected
* option, or {@code null} if the field's value is null or if no matching option exists.
*/
I18nString getI18nValue() {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (this.getValue() == null) {
return null;
}
return this.options?.get(this.getValue())
}

@Override
Field clone() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ class MultichoiceMapField extends MapOptionsField<I18nString, LinkedHashSet<Stri
this.defaultValue = new LinkedHashSet<>()
}

MultichoiceMapField(Map<String, I18nString> choices) {
super(choices)
MultichoiceMapField(Map<String, I18nString> options) {
super(options)
this.defaultValue = new LinkedHashSet<>()
}

MultichoiceMapField(Map<String, I18nString> choices, LinkedHashSet<String> defaultValues) {
this(choices)
MultichoiceMapField(Map<String, I18nString> options, LinkedHashSet<String> defaultValues) {
this(options)
this.defaultValue = defaultValues
}

Expand Down Expand Up @@ -46,6 +46,24 @@ class MultichoiceMapField extends MapOptionsField<I18nString, LinkedHashSet<Stri
super.setDefaultValue(defaultValue)
}

/**
* Returns a set of internationalized string values corresponding to the currently selected option keys.
* <p>
* This method maps each selected value (key) in the field's current value to its corresponding
* {@link I18nString} from the options map.
* </p>
*
* @return a {@link LinkedHashSet} of {@link I18nString} objects representing the internationalized
* values of the selected options. Returns an empty set if options are null, empty, or if
* the field's value is null.
*/
Set<I18nString> getI18nValue() {
if (this.options == null || this.options.isEmpty() || this.getValue() == null) {
return new LinkedHashSet<>()
}
return this.getValue().collect { this.options[it] } as LinkedHashSet
}

@Override
Field clone() {
MultichoiceMapField clone = new MultichoiceMapField()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2541,6 +2541,7 @@ class ActionDelegate {
* <pre>
* searchCase("case: processIdentifier eq 'query_test' and data.number_0.value == 3")
* searchCase("case: id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
* searchCase("id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
* </pre>
*
* @param query query language string starting with {@code case:}
Expand All @@ -2560,6 +2561,7 @@ class ActionDelegate {
* <pre>
* pagedSearchCases("cases: processIdentifier eq 'query_test' page 1 size 5 sort by title desc")
* pagedSearchCases("cases: author eq 'user@mail.com' and creationDate gt 2020-03-03")
* pagedSearchCases("author eq 'user@mail.com' and creationDate gt 2020-03-03")
* </pre>
*
* @param query query language string starting with {@code cases:}
Expand All @@ -2579,6 +2581,7 @@ class ActionDelegate {
* <pre>
* searchCases("cases: processIdentifier eq 'query_test' and data.boolean_0.value == true")
* searchCases("cases: title contains 'Test' sort by creationDate desc")
* searchCases("title contains 'Test' sort by creationDate desc")
* </pre>
*
* @param query query language string starting with {@code cases:}
Expand All @@ -2597,6 +2600,7 @@ class ActionDelegate {
* <pre>
* countCases("cases: processIdentifier eq 'query_test'")
* countCases("cases: data.boolean_0.value == true and data.text_0.value != '4'")
* countCases("data.boolean_0.value == true and data.text_0.value != '4'")
* </pre>
*
* @param query query language string starting with {@code cases:}
Expand All @@ -2615,6 +2619,7 @@ class ActionDelegate {
* <pre>
* existsCase("cases: processIdentifier eq 'query_test'")
* existsCase("cases: id in ('5f9b1c2d3e4f5a6b7c8d9e0f', '5f9b1c2d3e4f5a6b7c8d9e10')")
* existsCase("id in ('5f9b1c2d3e4f5a6b7c8d9e0f', '5f9b1c2d3e4f5a6b7c8d9e10')")
* </pre>
*
* @param query query language string starting with {@code cases:}
Expand All @@ -2633,6 +2638,7 @@ class ActionDelegate {
* <pre>
* searchTask("task: transitionId eq 't1' and caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
* searchTask("task: id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
* searchTask("id eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
* </pre>
*
* @param query query language string starting with {@code task:}
Expand All @@ -2652,6 +2658,7 @@ class ActionDelegate {
* <pre>
* pagedSearchTasks("tasks: title eq 'test' page 0 size 10 sort by lastFinish desc")
* pagedSearchTasks("tasks: userId eq 'user1' and state eq enabled")
* pagedSearchTasks("userId eq 'user1' and state eq enabled")
* </pre>
*
* @param query query language string starting with {@code tasks:}
Expand All @@ -2671,6 +2678,7 @@ class ActionDelegate {
* <pre>
* searchTasks("tasks: processId eq 'my_process' and userId in ('user1', 'user2')")
* searchTasks("tasks: title contains 'Approve' sort by title asc")
* searchTasks("title contains 'Approve' sort by title asc")
* </pre>
*
* @param query query language string starting with {@code tasks:}
Expand All @@ -2689,6 +2697,7 @@ class ActionDelegate {
* <pre>
* countTasks("tasks: caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
* countTasks("tasks: transitionId eq 't1' and userId eq 'user1'")
* countTasks("transitionId eq 't1' and userId eq 'user1'")
* </pre>
*
* @param query query language string starting with {@code tasks:}
Expand All @@ -2707,6 +2716,7 @@ class ActionDelegate {
* <pre>
* existsTask("tasks: caseId eq '5f9b1c2d3e4f5a6b7c8d9e0f'")
* existsTask("tasks: transitionId eq 't1' and userId not eq 'user1'")
* existsTask("transitionId eq 't1' and userId not eq 'user1'")
* </pre>
*
* @param query query language string starting with {@code tasks:}
Expand All @@ -2725,6 +2735,7 @@ class ActionDelegate {
* <pre>
* searchProcess("process: identifier == 'query_test'")
* searchProcess("process: identifier eq 'my_process' and version eq 1.0.0")
* searchProcess("identifier eq 'my_process' and version eq 1.0.0")
* </pre>
*
* @param query query language string starting with {@code process:}
Expand All @@ -2744,6 +2755,7 @@ class ActionDelegate {
* <pre>
* pagedSearchProcesses("processes: identifier eq 'my_process' page 0 size 10 sort by version desc")
* pagedSearchProcesses("processes: version in (1.0.0 : 2.0.0)")
* pagedSearchProcesses("version in (1.0.0 : 2.0.0)")
* </pre>
*
* @param query query language string starting with {@code processes:}
Expand All @@ -2763,6 +2775,7 @@ class ActionDelegate {
* <pre>
* searchProcesses("processes: title contains 'Test' sort by identifier asc")
* searchProcesses("processes: identifier in ('process_a', 'process_b')")
* searchProcesses("identifier in ('process_a', 'process_b')")
* </pre>
*
* @param query query language string starting with {@code processes:}
Expand All @@ -2781,6 +2794,7 @@ class ActionDelegate {
* <pre>
* countProcesses("processes: identifier eq 'my_process'")
* countProcesses("processes: version gte 1.0.0")
* countProcesses("version gte 1.0.0")
* </pre>
*
* @param query query language string starting with {@code processes:}
Expand All @@ -2799,6 +2813,7 @@ class ActionDelegate {
* <pre>
* existsProcess("processes: identifier eq 'my_process'")
* existsProcess("processes: version eq 1.0.0")
* existsProcess("version eq 1.0.0")
* </pre>
*
* @param query query language string starting with {@code processes:}
Expand All @@ -2817,6 +2832,7 @@ class ActionDelegate {
* <pre>
* searchUser("user: email eq 'user@mail.com'")
* searchUser("user: name eq 'John' and surname eq 'Doe'")
* searchUser("name eq 'John' and surname eq 'Doe'")
* </pre>
*
* @param query query language string starting with {@code user:}
Expand All @@ -2836,6 +2852,7 @@ class ActionDelegate {
* <pre>
* pagedSearchUsers("users: name eq 'John' page 0 size 25 sort by surname asc")
* pagedSearchUsers("users: email contains '@company.com'")
* pagedSearchUsers("email contains '@company.com'")
* </pre>
*
* @param query query language string starting with {@code users:}
Expand All @@ -2855,6 +2872,7 @@ class ActionDelegate {
* <pre>
* searchUsers("users: surname eq 'Doe' sort by name asc")
* searchUsers("users: email in ('a@mail.com', 'b@mail.com')")
* searchUsers("email in ('a@mail.com', 'b@mail.com')")
* </pre>
*
* @param query query language string starting with {@code users:}
Expand All @@ -2873,6 +2891,7 @@ class ActionDelegate {
* <pre>
* countUsers("users: email contains '@company.com'")
* countUsers("users: name eq 'John'")
* countUsers("name eq 'John'")
* </pre>
*
* @param query query language string starting with {@code users:}
Expand All @@ -2891,6 +2910,7 @@ class ActionDelegate {
* <pre>
* existsUser("users: email eq 'user@mail.com'")
* existsUser("users: name eq 'John' and surname eq 'Doe'")
* existsUser("name eq 'John' and surname eq 'Doe'")
* </pre>
*
* @param query query language string starting with {@code users:}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.netgrif.application.engine.pfql.service;

import com.netgrif.application.engine.pfql.service.formatters.QueryLangPlaceholderHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;

import java.util.List;

import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.*;
import static com.netgrif.application.engine.pfql.service.utils.SearchUtils.buildResourcePrefix;

/**
* Abstract base class for resource search services providing shared query pre-processing.
* <p>
* Handles common pre-processing steps:
* <ol>
* <li>Formatter bracket substitution – fills {@code {}} placeholders with provided arguments</li>
* <li>PFQL prefix check/injection – implemented individually by each subclass</li>
* </ol>
*/
@Slf4j
@RequiredArgsConstructor
public abstract class AbstractResourceSearchService<Resource> implements IResourceSearchService<Resource> {

protected final QueryLangPlaceholderHandler placeholderHandler;

/**
* Pre-processes the raw query string: fills {@code {}} placeholders and ensures a correct PFQL prefix.
*
* @param rawQuery the raw query string, possibly with {@code {}} placeholders
* @param isMulti if the prefix should address multiple resources
* @param args arguments to substitute into {@code {}} placeholders (in order)
* @return the fully pre-processed query string ready for evaluation
*/
protected String preProcess(String rawQuery, boolean isMulti, Object... args) {
String formatted = formatPlaceholders(rawQuery, placeholderHandler, args);
return ensurePrefix(formatted, isMulti);
}


protected abstract String ensurePrefix(String query, boolean isMulti);

protected abstract Resource doSearchOne(QueryLangEvaluator evaluator);

protected abstract Page<Resource> doSearchAll(QueryLangEvaluator evaluator);

protected abstract long doCount(QueryLangEvaluator evaluator);

protected abstract boolean doExists(QueryLangEvaluator evaluator);

/**
* Ensures the query string has the correct PFQL resource prefix.
* Each implementation defines which prefix is expected and how to inject it if missing.
*
* @param query the query string after placeholder substitution
* @param isMulti if the prefix should address multiple resources
* @param multiPrefixToken token of prefix to search multiple resources
* @param singlePrefixToken token of prefix to search single resource
* @return the query string with the correct prefix guaranteed
*/
protected String doEnsurePrefix(String query, boolean isMulti, int multiPrefixToken, int singlePrefixToken) {
if (query == null || hasResourcePrefix(query, List.of(multiPrefixToken, singlePrefixToken))) {
return query;
}
return buildResourcePrefix(isMulti ? multiPrefixToken : singlePrefixToken) + query;
}

@Override
public Resource searchOne(String queryString, Object... args) {
final String processedQuery = preProcess(queryString, false, args);
log.debug("Searching one with query: {}", processedQuery);
return searchOne(evaluateQuery(processedQuery));
}

@Override
public Resource searchOne(QueryLangEvaluator evaluator) {
checkEvaluatorNotNull(evaluator);
checkEvaluatorIsSingle(evaluator);
checkEvaluatorResourceType(evaluator);
return doSearchOne(evaluator);
}

@Override
public Page<Resource> searchAll(String queryString, Object... args) {
final String processedQuery = preProcess(queryString, true, args);
log.debug("Searching all with query: {}", processedQuery);
return searchAll(evaluateQuery(processedQuery));
}

@Override
public Page<Resource> searchAll(QueryLangEvaluator evaluator) {
checkEvaluatorNotNull(evaluator);
checkEvaluatorIsMultiple(evaluator);
checkEvaluatorResourceType(evaluator);
return doSearchAll(evaluator);
}

@Override
public long count(String queryString, Object... args) {
final String processedQuery = preProcess(queryString, true, args);
log.debug("Counting with query: {}", processedQuery);
return count(evaluateQuery(processedQuery));
}

@Override
public long count(QueryLangEvaluator evaluator) {
checkEvaluatorNotNull(evaluator);
checkEvaluatorResourceType(evaluator);
return doCount(evaluator);
}

@Override
public boolean exists(String queryString, Object... args) {
final String processedQuery = preProcess(queryString, false, args);
log.debug("Checking existence with query: {}", processedQuery);
return exists(evaluateQuery(processedQuery));
}

@Override
public boolean exists(QueryLangEvaluator evaluator) {
checkEvaluatorNotNull(evaluator);
checkEvaluatorResourceType(evaluator);
return doExists(evaluator);
}
}
Loading
Loading