Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use BPMN errorMessageVariable #48

Merged
merged 3 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 0 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,6 @@
<version>${dsf.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.12.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,57 +1,38 @@
package de.medizininformatik_initiative.process.report.service;

import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.camunda.bpm.engine.delegate.DelegateExecution;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.ResourceType;
import org.hl7.fhir.r4.model.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.beans.factory.InitializingBean;

import de.medizininformatik_initiative.process.report.ConstantsReport;
import de.medizininformatik_initiative.process.report.util.SearchQueryCheckService;
import dev.dsf.bpe.v1.ProcessPluginApi;
import dev.dsf.bpe.v1.activity.AbstractServiceDelegate;
import dev.dsf.bpe.v1.variables.Target;
import dev.dsf.bpe.v1.variables.Variables;

public class CheckSearchBundle extends AbstractServiceDelegate
public class CheckSearchBundle extends AbstractServiceDelegate implements InitializingBean
{
private static final Logger logger = LoggerFactory.getLogger(CheckSearchBundle.class);

private static final Pattern MODIFIERS = Pattern.compile(":.*");
private static final Pattern YEAR_ONLY = Pattern.compile("\\b20\\d{2}(?!\\S)");
private static final String DATE_EQUALITY_FILTER = "eq";
private final SearchQueryCheckService searchQueryCheckService;

private static final String CAPABILITY_STATEMENT_PATH = "metadata";
private static final String SUMMARY_SEARCH_PARAM = "_summary";
private static final String SUMMARY_SEARCH_PARAM_VALUE_COUNT = "count";
private static final String TYPE_SEARCH_PARAM = "type";

private static final Set<String> ALL_RESOURCE_TYPES = EnumSet.allOf(ResourceType.class).stream()
.map(ResourceType::name).collect(Collectors.toSet());

private static final List<String> DATE_SEARCH_PARAMS = List.of("date", "recorded-date", "onset-date", "effective",
"effective-time", "authored", "collected", "issued", "period", "location-period", "occurrence");
private static final List<String> TOKEN_SEARCH_PARAMS = List.of("code", "ingredient-code", "type");
private static final List<String> OTHER_SEARCH_PARAMS = List.of("_profile", "_summary");
private static final List<String> VALID_SEARCH_PARAMS = Stream
.of(DATE_SEARCH_PARAMS.stream(), TOKEN_SEARCH_PARAMS.stream(), OTHER_SEARCH_PARAMS.stream()).flatMap(s -> s)
.toList();

public CheckSearchBundle(ProcessPluginApi api)
public CheckSearchBundle(ProcessPluginApi api, SearchQueryCheckService searchQueryCheckService)
{
super(api);
this.searchQueryCheckService = searchQueryCheckService;
}

@Override
public void afterPropertiesSet() throws Exception
{
super.afterPropertiesSet();
Objects.requireNonNull(searchQueryCheckService, "searchQueryCheckService");
}

@Override
Expand All @@ -66,15 +47,12 @@ protected void doExecute(DelegateExecution execution, Variables variables)

try
{
List<Bundle.BundleEntryComponent> searches = bundle.getEntry();

testNoResources(searches);
testRequestMethod(searches);
testRequestUrls(searches);
searchQueryCheckService.checkBundle(bundle);

logger.info(
"Search Bundle downloaded from HRP '{}' as part of Task with id '{}' contains only valid requests of type GET and valid search params {}",
target.getOrganizationIdentifierValue(), task.getId(), VALID_SEARCH_PARAMS);
target.getOrganizationIdentifierValue(), task.getId(),
searchQueryCheckService.getValidSearchParams());
}
catch (Exception exception)
{
Expand All @@ -86,155 +64,4 @@ protected void doExecute(DelegateExecution execution, Variables variables)
exception);
}
}

private void testNoResources(List<Bundle.BundleEntryComponent> searches)
{
if (searches.stream().map(Bundle.BundleEntryComponent::getResource).anyMatch(Objects::nonNull))
throw new RuntimeException("Search Bundle contains resources");
}

private void testRequestMethod(List<Bundle.BundleEntryComponent> searches)
{
long searchesCount = searches.size();
long httpGetCount = searches.stream().filter(Bundle.BundleEntryComponent::hasRequest)
.map(Bundle.BundleEntryComponent::getRequest).filter(Bundle.BundleEntryRequestComponent::hasMethod)
.map(Bundle.BundleEntryRequestComponent::getMethod).filter(Bundle.HTTPVerb.GET::equals).count();

if (searchesCount != httpGetCount)
throw new RuntimeException("Search Bundle contains HTTP method other then GET");
}

private void testRequestUrls(List<Bundle.BundleEntryComponent> searches)
{
int searchesCount = searches.size();
List<Bundle.BundleEntryRequestComponent> requests = searches.stream()
.filter(Bundle.BundleEntryComponent::hasRequest).map(Bundle.BundleEntryComponent::getRequest)
.filter(Bundle.BundleEntryRequestComponent::hasUrl).toList();
int requestCount = requests.size();

if (searchesCount != requestCount)
throw new RuntimeException("Search Bundle contains request without url");

List<UriComponents> uriComponents = requests.stream()
.map(r -> UriComponentsBuilder.fromUriString(r.getUrl()).build()).toList();

testContainsOnlyResourcePath(uriComponents);
testContainsValidSummaryCount(uriComponents);
testContainsValidSearchParams(uriComponents);
testContainsValidDateSearchParams(uriComponents);
testContainsValidTokenSearchParams(uriComponents);
}

private void testContainsOnlyResourcePath(List<UriComponents> uriComponents)
{
uriComponents.stream().filter(u -> !CAPABILITY_STATEMENT_PATH.equals(u.getPath())).forEach(this::testPath);
}

private void testPath(UriComponents uriComponents)
{
if (!ALL_RESOURCE_TYPES.contains(uriComponents.getPath()))
{
throw new RuntimeException(
"Search Bundle contains request url with forbidden path - [" + uriComponents.getPath() + "]");
}
}

private void testContainsValidSummaryCount(List<UriComponents> uriComponents)
{
uriComponents.stream().filter(u -> !CAPABILITY_STATEMENT_PATH.equals(u.getPath()))
.map(UriComponents::getQueryParams).forEach(this::testSummaryCount);
}

private void testSummaryCount(MultiValueMap<String, String> queryParams)
{
List<String> summaryParams = queryParams.get(SUMMARY_SEARCH_PARAM);

if (summaryParams == null || summaryParams.isEmpty())
{
throw new RuntimeException("Search Bundle contains request url without _summary parameter");
}

if (summaryParams.size() > 1)
{
throw new RuntimeException("Search Bundle contains request url with more than one _summary parameter");
}

if (!SUMMARY_SEARCH_PARAM_VALUE_COUNT.equals(summaryParams.get(0)))
{
throw new RuntimeException(
"Search Bundle contains request url with unexpected _summary parameter value (expected: count, actual: "
+ summaryParams.get(0) + ")");
}
}

private void testContainsValidSearchParams(List<UriComponents> uriComponents)
{
uriComponents.stream().filter(u -> !CAPABILITY_STATEMENT_PATH.equals(u.getPath()))
.map(UriComponents::getQueryParams).forEach(this::testSearchParamNames);
}

private void testSearchParamNames(MultiValueMap<String, String> queryParams)
{
if (queryParams.keySet().stream().map(s -> MODIFIERS.matcher(s).replaceAll(""))
.anyMatch(s -> !VALID_SEARCH_PARAMS.contains(s)))
throw new RuntimeException("Search Bundle contains invalid search params, only allowed search params are "
+ VALID_SEARCH_PARAMS);
}

private void testContainsValidDateSearchParams(List<UriComponents> uriComponents)
{
uriComponents.stream().filter(u -> !CAPABILITY_STATEMENT_PATH.equals(u.getPath()))
.map(UriComponents::getQueryParams).forEach(this::testSearchParamDateValues);
}

private void testSearchParamDateValues(MultiValueMap<String, String> queryParams)
{
List<Map.Entry<String, String>> dateParams = queryParams.entrySet().stream()
.filter(e -> DATE_SEARCH_PARAMS.contains(MODIFIERS.matcher(e.getKey()).replaceAll("")))
.flatMap(e -> e.getValue().stream().map(v -> Map.entry(e.getKey(), v))).toList();

List<Map.Entry<String, String>> erroneousDateFilters = dateParams.stream()
.filter(e -> !e.getValue().startsWith(DATE_EQUALITY_FILTER)).toList();

if (erroneousDateFilters.size() > 0)
throw new RuntimeException(
"Search Bundle contains date search params not starting with 'eq' - [" + erroneousDateFilters
.stream().map(e -> e.getKey() + ":" + e.getValue()).collect(Collectors.joining(",")) + "]");

List<Map.Entry<String, String>> erroneousDateValues = dateParams.stream()
.filter(e -> !YEAR_ONLY.matcher(e.getValue().replace(DATE_EQUALITY_FILTER, "")).matches()).toList();

if (erroneousDateValues.size() > 0)
throw new RuntimeException(
"Search Bundle contains date search params not limited to a year - [" + erroneousDateValues.stream()
.map(e -> e.getKey() + ":" + e.getValue()).collect(Collectors.joining(",")) + "]");
}

private void testContainsValidTokenSearchParams(List<UriComponents> uriComponents)
{
uriComponents.stream().filter(u -> !CAPABILITY_STATEMENT_PATH.equals(u.getPath()))
.forEach(this::testSearchParamTokenValues);
}

private void testSearchParamTokenValues(UriComponents uriComponents)
{
List<Map.Entry<String, String>> codeParams = uriComponents.getQueryParams().entrySet().stream()
.filter(e -> TOKEN_SEARCH_PARAMS.contains(MODIFIERS.matcher(e.getKey()).replaceAll("")))
.flatMap(e -> e.getValue().stream().map(v -> Map.entry(e.getKey(), v))).toList();

// Exemption for Encounter.type token params
List<Map.Entry<String, String>> erroneousCodeValues = codeParams.stream()
.filter(e -> !e.getValue().endsWith("|"))
.filter(e -> !isEncounterType(uriComponents.getPath(), e.getKey())).toList();

if (erroneousCodeValues.size() > 0)
throw new RuntimeException(
"Search Bundle contains code search params not limited to system - [" + erroneousCodeValues.stream()
.map(e -> e.getKey() + ":" + e.getValue()).collect(Collectors.joining(",")) + "]");
}

private boolean isEncounterType(String path, String paramName)
{
return TYPE_SEARCH_PARAM.equals(paramName) && ResourceType.Encounter.name().equals(path);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,12 @@ protected void doExecute(DelegateExecution execution, Variables variables)
ConstantsReport.CODESYSTEM_REPORT_STATUS_VALUE_RECEIVE_ERROR, "Download report failed"));
variables.updateTask(task);

variables.setString(ConstantsReport.BPMN_EXECUTION_VARIABLE_REPORT_RECEIVE_ERROR_MESSAGE,
"Download report failed");

logger.warn(
"Downloading report with id '{}' from organization '{}' referenced in Task with id '{}' failed - {}",
reportReference.getValue(), task.getRequester().getIdentifier().getValue(), task.getId(),
exception.getMessage());
throw new BpmnError(ConstantsReport.BPMN_EXECUTION_VARIABLE_REPORT_RECEIVE_ERROR,
"Download report - " + exception.getMessage());
"Download report failed - " + exception.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,10 @@ protected void doExecute(DelegateExecution execution, Variables variables)
ConstantsReport.CODESYSTEM_REPORT_STATUS_VALUE_RECEIVE_ERROR, "Insert report failed"));
variables.updateTask(task);

variables.setString(ConstantsReport.BPMN_EXECUTION_VARIABLE_REPORT_RECEIVE_ERROR_MESSAGE,
"Insert report failed");

logger.warn("Storing report from organization '{}' for Task with id '{}' failed - {}", sendingOrganization,
task.getId(), exception.getMessage());
throw new BpmnError(ConstantsReport.BPMN_EXECUTION_VARIABLE_REPORT_RECEIVE_ERROR,
"Insert report - " + exception.getMessage());
"Insert report failed - " + exception.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import de.medizininformatik_initiative.process.report.service.SetTimer;
import de.medizininformatik_initiative.process.report.service.StoreReceipt;
import de.medizininformatik_initiative.process.report.util.ReportStatusGenerator;
import de.medizininformatik_initiative.process.report.util.SearchQueryCheckService;
import dev.dsf.bpe.v1.ProcessPluginApi;
import dev.dsf.bpe.v1.ProcessPluginDeploymentStateListener;
import dev.dsf.bpe.v1.documentation.ProcessDocumentation;
Expand Down Expand Up @@ -94,7 +95,14 @@ public DownloadSearchBundle downloadSearchBundle()
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public CheckSearchBundle checkSearchBundle()
{
return new CheckSearchBundle(api);
return new CheckSearchBundle(api, searchQueryCheckService());
}

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public SearchQueryCheckService searchQueryCheckService()
{
return new SearchQueryCheckService();
}

@Bean
Expand Down
Loading
Loading