From 66639647c5222ac71b57bbfbf006265d9385357b Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Tue, 27 May 2025 18:28:59 +0200 Subject: [PATCH 1/4] improved javadoc, overridden default execute in MessageSendTask Added throws ErrorBoundaryEvent to default execute method of MessageSendTask to document that custom error handling flows can be implemented in BPMN by throwing ErrorBoundaryEvent --- .../dsf/bpe/v2/activity/MessageSendTask.java | 33 +++++++++++++++++++ .../dev/dsf/bpe/v2/activity/ServiceTask.java | 13 ++++++++ 2 files changed, 46 insertions(+) diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/MessageSendTask.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/MessageSendTask.java index 1f7050665..323b7efff 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/MessageSendTask.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/MessageSendTask.java @@ -1,10 +1,43 @@ package dev.dsf.bpe.v2.activity; +import org.hl7.fhir.r4.model.Task; + +import dev.dsf.bpe.v2.ProcessPluginApi; +import dev.dsf.bpe.v2.activity.task.BusinessKeyStrategy; +import dev.dsf.bpe.v2.activity.task.TaskSender; +import dev.dsf.bpe.v2.activity.values.SendTaskValues; +import dev.dsf.bpe.v2.error.ErrorBoundaryEvent; import dev.dsf.bpe.v2.error.MessageSendTaskErrorHandler; import dev.dsf.bpe.v2.error.impl.DefaultMessageSendTaskErrorHandler; +import dev.dsf.bpe.v2.variables.Variables; public interface MessageSendTask extends MessageActivity { + /** + * Default implementation uses a {@link TaskSender} from + * {@link #getTaskSender(ProcessPluginApi, Variables, SendTaskValues)} to send {@link Task} resources with the + * {@link BusinessKeyStrategy} from {@link #getBusinessKeyStrategy()}. No {@link ErrorBoundaryEvent} are thrown by + * the default implementation. + * + * @param api + * not null + * @param variables + * not null + * @param sendTaskValues + * not null + * @throws ErrorBoundaryEvent + * to trigger custom error handling flow in BPMN, when using {@link DefaultMessageSendTaskErrorHandler} + * @throws Exception + * to fail the FHIR {@link Task} and stop the process instance, when using + * {@link DefaultMessageSendTaskErrorHandler} + */ + @Override + default void execute(ProcessPluginApi api, Variables variables, SendTaskValues sendTaskValues) + throws ErrorBoundaryEvent, Exception + { + getTaskSender(api, variables, sendTaskValues).send(); + } + @Override default MessageSendTaskErrorHandler getErrorHandler() { diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/ServiceTask.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/ServiceTask.java index ec920b732..4e9a5ad33 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/ServiceTask.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/ServiceTask.java @@ -1,5 +1,7 @@ package dev.dsf.bpe.v2.activity; +import org.hl7.fhir.r4.model.Task; + import dev.dsf.bpe.v2.ProcessPluginApi; import dev.dsf.bpe.v2.error.ErrorBoundaryEvent; import dev.dsf.bpe.v2.error.ServiceTaskErrorHandler; @@ -8,6 +10,17 @@ public interface ServiceTask extends Activity { + /** + * @param api + * not null + * @param variables + * not null + * @throws ErrorBoundaryEvent + * to trigger custom error handling flow in BPMN, when using {@link DefaultServiceTaskErrorHandler} + * @throws Exception + * to fail the FHIR {@link Task} and stop the process instance, when using + * {@link DefaultServiceTaskErrorHandler} + */ void execute(ProcessPluginApi api, Variables variables) throws ErrorBoundaryEvent, Exception; @Override From 6ed6d7017e0c016d7dee94e4c74d2d2752a54d62 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 28 May 2025 13:15:36 +0200 Subject: [PATCH 2/4] API v2 target provider impl and integration tests --- .../dev/dsf/bpe/v2/ProcessPluginApiImpl.java | 12 +- .../v2/service/AbstractResourceProvider.java | 20 +- .../bpe/v2/service/TargetProviderImpl.java | 199 ++++++++++++ .../dsf/bpe/v2/spring/ApiServiceConfig.java | 10 +- .../dev/dsf/bpe/v2/variables/TargetsImpl.java | 6 + .../java/dev/dsf/bpe/v2/ProcessPluginApi.java | 3 + .../dsf/bpe/v2/constants/NamingSystems.java | 41 ++- .../dsf/bpe/v2/service/TargetProvider.java | 81 +++++ .../dev/dsf/bpe/v2/variables/Targets.java | 5 + .../integration/PluginV2IntegrationTest.java | 6 + .../bpe/test/service/TargetProviderTest.java | 300 ++++++++++++++++++ .../dsf/bpe/test/spring/config/Config.java | 3 +- .../src/main/resources/bpe/test.bpmn | 24 ++ 13 files changed, 698 insertions(+), 12 deletions(-) create mode 100644 dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/TargetProviderImpl.java create mode 100644 dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/TargetProvider.java create mode 100644 dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/TargetProviderTest.java diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiImpl.java index 409ddc388..d222a5764 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiImpl.java @@ -18,6 +18,7 @@ import dev.dsf.bpe.v2.service.OrganizationProvider; import dev.dsf.bpe.v2.service.QuestionnaireResponseHelper; import dev.dsf.bpe.v2.service.ReadAccessHelper; +import dev.dsf.bpe.v2.service.TargetProvider; import dev.dsf.bpe.v2.service.TaskHelper; import dev.dsf.bpe.v2.service.process.ProcessAuthorizationHelper; @@ -39,6 +40,7 @@ public class ProcessPluginApiImpl implements ProcessPluginApi, InitializingBean private final ReadAccessHelper readAccessHelper; private final TaskHelper taskHelper; private final CryptoService cryptoService; + private final TargetProvider targetProvider; public ProcessPluginApiImpl(ProxyConfig proxyConfig, EndpointProvider endpointProvider, FhirContext fhirContext, DsfClientProvider dsfClientProvider, FhirClientProvider fhirClientProvider, @@ -46,7 +48,7 @@ public ProcessPluginApiImpl(ProxyConfig proxyConfig, EndpointProvider endpointPr ObjectMapper objectMapper, OrganizationProvider organizationProvider, ProcessAuthorizationHelper processAuthorizationHelper, QuestionnaireResponseHelper questionnaireResponseHelper, ReadAccessHelper readAccessHelper, - TaskHelper taskHelper, CryptoService cryptoService) + TaskHelper taskHelper, CryptoService cryptoService, TargetProvider targetProvider) { this.proxyConfig = proxyConfig; this.endpointProvider = endpointProvider; @@ -63,6 +65,7 @@ public ProcessPluginApiImpl(ProxyConfig proxyConfig, EndpointProvider endpointPr this.readAccessHelper = readAccessHelper; this.taskHelper = taskHelper; this.cryptoService = cryptoService; + this.targetProvider = targetProvider; } @Override @@ -82,6 +85,7 @@ public void afterPropertiesSet() throws Exception Objects.requireNonNull(readAccessHelper, "readAccessHelper"); Objects.requireNonNull(taskHelper, "taskHelper"); Objects.requireNonNull(cryptoService, "cryptoService"); + Objects.requireNonNull(targetProvider, "targetProvider"); } @Override @@ -173,4 +177,10 @@ public CryptoService getCryptoService() { return cryptoService; } + + @Override + public TargetProvider getTargetProvider() + { + return targetProvider; + } } diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/AbstractResourceProvider.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/AbstractResourceProvider.java index 7c0e1c5e7..73c2d5adf 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/AbstractResourceProvider.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/AbstractResourceProvider.java @@ -7,7 +7,9 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Stream; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; @@ -49,7 +51,15 @@ protected final List search(Class se Map> searchParameters, SearchEntryMode targetMode, Class targetType, Predicate filter) { - List organizations = new ArrayList<>(); + return search(searchType, searchParameters).filter(e -> targetMode.equals(e.getSearch().getMode())) + .filter(BundleEntryComponent::hasResource).map(BundleEntryComponent::getResource) + .filter(targetType::isInstance).map(targetType::cast).filter(filter).toList(); + } + + protected final Stream search(Class searchType, + Map> searchParameters) + { + List> resources = new ArrayList<>(); boolean hasMore = true; int page = 1; @@ -57,15 +67,13 @@ protected final List search(Class se { Bundle resultBundle = search(searchType, searchParameters, page++); - organizations.addAll(resultBundle.getEntry().stream().filter(BundleEntryComponent::hasSearch) - .filter(e -> targetMode.equals(e.getSearch().getMode())).filter(BundleEntryComponent::hasResource) - .map(BundleEntryComponent::getResource).filter(targetType::isInstance).map(targetType::cast) - .filter(filter).toList()); + resources.add(resultBundle.getEntry().stream().filter(BundleEntryComponent::hasSearch) + .filter(BundleEntryComponent::hasResource)); hasMore = resultBundle.getLink(LINK_NEXT) != null; } - return organizations; + return resources.stream().flatMap(Function.identity()); } private Bundle search(Class searchType, Map> parameters, int page) diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/TargetProviderImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/TargetProviderImpl.java new file mode 100644 index 000000000..4a69b285a --- /dev/null +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/TargetProviderImpl.java @@ -0,0 +1,199 @@ +package dev.dsf.bpe.v2.service; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Stream; + +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Bundle.SearchEntryMode; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Endpoint; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.OrganizationAffiliation; + +import dev.dsf.bpe.v2.constants.NamingSystems.EndpointIdentifier; +import dev.dsf.bpe.v2.constants.NamingSystems.OrganizationIdentifier; +import dev.dsf.bpe.v2.variables.Target; +import dev.dsf.bpe.v2.variables.TargetImpl; +import dev.dsf.bpe.v2.variables.Targets; +import dev.dsf.bpe.v2.variables.TargetsImpl; + +public class TargetProviderImpl extends AbstractResourceProvider implements TargetProvider +{ + public static class BuilderImpl implements Builder + { + private static final record OrganizationAffiliationAndOrganizationAndEndpoint( + OrganizationAffiliation affiliation, Organization member, Endpoint endpoint) + { + } + + private final List affiliations = new ArrayList<>(); + private final Map organizationsById = new HashMap<>(); + private final Map endpointsById = new HashMap<>(); + + private final Set memberOrganizationIdentifiers = new HashSet<>(); + + public BuilderImpl(Identifier[] memberOrganizationIdentifiers) + { + if (memberOrganizationIdentifiers != null) + this.memberOrganizationIdentifiers.addAll(Arrays.stream(memberOrganizationIdentifiers) + .filter(Objects::nonNull).map(this::identifierToString).toList()); + } + + private String identifierToString(Identifier i) + { + return i.getSystem() + "|" + i.getValue(); + } + + private Predicate filter; + + protected Target createTarget(String organizationIdentifierValue, String endpointIdentifierValue, + String endpointAddress, String correlationKey) + { + Objects.requireNonNull(organizationIdentifierValue, "organizationIdentifierValue"); + Objects.requireNonNull(endpointIdentifierValue, "endpointIdentifierValue"); + Objects.requireNonNull(endpointAddress, "endpointAddress"); + + return new TargetImpl(organizationIdentifierValue, endpointIdentifierValue, endpointAddress, + correlationKey); + } + + @Override + public Targets withCorrelationKey() + { + return toTargets(true); + } + + @Override + public Targets withoutCorrelationKey() + { + return toTargets(false); + } + + private Targets toTargets(boolean withCorrelationKey) + { + List targets = affiliations.stream() + .filter(a -> organizationsById + .containsKey(a.getParticipatingOrganization().getReferenceElement().getIdPart()) + && endpointsById.containsKey(a.getEndpointFirstRep().getReferenceElement().getIdPart())) + .map(a -> new OrganizationAffiliationAndOrganizationAndEndpoint(a, + organizationsById.get(a.getParticipatingOrganization().getReferenceElement().getIdPart()), + endpointsById.get(a.getEndpointFirstRep().getReferenceElement().getIdPart()))) + .filter(a -> OrganizationIdentifier.hasIdentifier(a.member) + && EndpointIdentifier.hasIdentifier(a.endpoint) && a.endpoint.hasAddressElement() + && a.endpoint.getAddressElement().hasValue()) + .filter(a -> memberOrganizationIdentifiers.isEmpty() ? true + : memberOrganizationIdentifiers.contains( + OrganizationIdentifier.findFirst(a.member).map(this::identifierToString).get())) + .filter(a -> filter == null ? true : filter.test(a.affiliation, a.member, a.endpoint)).map(a -> + { + String organizationIdentifierValue = OrganizationIdentifier.findFirst(a.member) + .map(Identifier::getValue).get(); + String endpointIdentifierValue = EndpointIdentifier.findFirst(a.endpoint) + .map(Identifier::getValue).get(); + + return new TargetImpl(organizationIdentifierValue, endpointIdentifierValue, + a.endpoint.getAddress(), withCorrelationKey ? UUID.randomUUID().toString() : null); + }).toList(); + + return new TargetsImpl(targets); + } + + @Override + public Builder filter(Predicate filter) + { + this.filter = filter; + + return this; + } + } + + public TargetProviderImpl(DsfClientProvider clientProvider, String localEndpointAddress) + { + super(clientProvider, localEndpointAddress); + } + + protected BuilderImpl createBuilder(Identifier... memberOrganizationIdentifier) + { + return new BuilderImpl(memberOrganizationIdentifier); + } + + private Builder toBuilder(Stream entries, Identifier... memberOrganizationIdentifier) + { + BuilderImpl builder = createBuilder(memberOrganizationIdentifier); + + entries.forEach(c -> + { + SearchEntryMode mode = c.getSearch().getMode(); + + if (SearchEntryMode.MATCH.equals(mode) && c.getResource() instanceof OrganizationAffiliation a) + builder.affiliations.add(a); + + else if (SearchEntryMode.INCLUDE.equals(mode)) + { + if (c.getResource() instanceof Organization o) + builder.organizationsById.put(o.getIdElement().getIdPart(), o); + else if (c.getResource() instanceof Endpoint e) + builder.endpointsById.put(e.getIdElement().getIdPart(), e); + } + }); + + return builder; + } + + @Override + public Builder create(Identifier parentOrganizationIdentifier) + { + Objects.requireNonNull(parentOrganizationIdentifier, "parentOrganizationIdentifier"); + + Stream entries = search(OrganizationAffiliation.class, + Map.of("active", List.of("true"), "primary-organization:identifier", + List.of(toSearchParameter(parentOrganizationIdentifier)), "_include", + List.of("OrganizationAffiliation:endpoint:Endpoint", + "OrganizationAffiliation:participating-organization:Organization"))); + + return toBuilder(entries); + } + + @Override + public Builder create(Identifier parentOrganizationIdentifier, Coding memberOrganizationRole) + { + Objects.requireNonNull(parentOrganizationIdentifier, "parentOrganizationIdentifier"); + Objects.requireNonNull(memberOrganizationRole, "memberOrganizationRole"); + + Stream entries = search(OrganizationAffiliation.class, + Map.of("active", List.of("true"), "primary-organization:identifier", + List.of(toSearchParameter(parentOrganizationIdentifier)), "_include", + List.of("OrganizationAffiliation:endpoint:Endpoint", + "OrganizationAffiliation:participating-organization:Organization"), + "role", List.of(toSearchParameter(memberOrganizationRole)))); + + return toBuilder(entries); + } + + @Override + public Builder create(Identifier parentOrganizationIdentifier, Coding memberOrganizationRole, + Identifier... memberOrganizationIdentifier) + { + Objects.requireNonNull(parentOrganizationIdentifier, "parentOrganizationIdentifier"); + Objects.requireNonNull(memberOrganizationRole, "memberOrganizationRole"); + Objects.requireNonNull(memberOrganizationIdentifier, "memberOrganizationIdentifier"); + + Stream entries = search(OrganizationAffiliation.class, + Map.of("active", List.of("true"), "primary-organization:identifier", + List.of(toSearchParameter(parentOrganizationIdentifier)), "_include", + List.of("OrganizationAffiliation:endpoint:Endpoint", + "OrganizationAffiliation:participating-organization:Organization"), + "role", List.of(toSearchParameter(memberOrganizationRole)))); + + return toBuilder(entries, memberOrganizationIdentifier); + } +} diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/spring/ApiServiceConfig.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/spring/ApiServiceConfig.java index 77b4d1e48..6fb0db2d2 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/spring/ApiServiceConfig.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/spring/ApiServiceConfig.java @@ -58,6 +58,8 @@ import dev.dsf.bpe.v2.service.QuestionnaireResponseHelperImpl; import dev.dsf.bpe.v2.service.ReadAccessHelper; import dev.dsf.bpe.v2.service.ReadAccessHelperImpl; +import dev.dsf.bpe.v2.service.TargetProvider; +import dev.dsf.bpe.v2.service.TargetProviderImpl; import dev.dsf.bpe.v2.service.TaskHelper; import dev.dsf.bpe.v2.service.TaskHelperImpl; import dev.dsf.bpe.v2.service.detector.CombinedDetectors; @@ -99,7 +101,7 @@ public ProcessPluginApi processPluginApiV2() return new ProcessPluginApiImpl(proxyConfigDelegate(), endpointProvider(), fhirContext(), dsfClientProvider(), fhirClientProvider(), oidcClientProvider(), mailService(), mimetypeService(), objectMapper(), organizationProvider(), processAuthorizationHelper(), questionnaireResponseHelper(), readAccessHelper(), - taskHelper(), cryptoService()); + taskHelper(), cryptoService(), targetProvider()); } @Bean @@ -285,4 +287,10 @@ public CryptoService cryptoService() { return new CryptoServiceImpl(); } + + @Bean + public TargetProvider targetProvider() + { + return new TargetProviderImpl(dsfClientProvider(), dsfClientConfig.getLocalConfig().getBaseUrl()); + } } diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/variables/TargetsImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/variables/TargetsImpl.java index a46e153d2..7a7af97ce 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/variables/TargetsImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/variables/TargetsImpl.java @@ -66,6 +66,12 @@ public boolean isEmpty() return entries.isEmpty(); } + @Override + public int size() + { + return entries.size(); + } + @Override public String toString() { diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/ProcessPluginApi.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/ProcessPluginApi.java index 12fe331c6..7e4c70a8b 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/ProcessPluginApi.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/ProcessPluginApi.java @@ -17,6 +17,7 @@ import dev.dsf.bpe.v2.service.OrganizationProvider; import dev.dsf.bpe.v2.service.QuestionnaireResponseHelper; import dev.dsf.bpe.v2.service.ReadAccessHelper; +import dev.dsf.bpe.v2.service.TargetProvider; import dev.dsf.bpe.v2.service.TaskHelper; import dev.dsf.bpe.v2.service.process.ProcessAuthorizationHelper; import dev.dsf.bpe.v2.variables.Variables; @@ -58,4 +59,6 @@ public interface ProcessPluginApi TaskHelper getTaskHelper(); CryptoService getCryptoService(); + + TargetProvider getTargetProvider(); } diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/constants/NamingSystems.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/constants/NamingSystems.java index f14334e0b..454ae9b9d 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/constants/NamingSystems.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/constants/NamingSystems.java @@ -13,8 +13,6 @@ import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.Task; -import dev.dsf.bpe.v2.constants.NamingSystems; - /** * Constants defining standard DSF NamingSystems */ @@ -32,7 +30,24 @@ private static Optional findFirst(Supplier> identif List identifiers = identifierSupplier.get(); return identifiers == null ? Optional.empty() - : identifiers.stream().filter(i -> identifierSystem.equals(i.getSystem())).findFirst(); + : identifiers.stream() + .filter(i -> i.hasSystemElement() && i.getSystemElement().hasValue() && i.hasValueElement() + && i.getValueElement().hasValue() + && identifierSystem.equals(i.getSystemElement().getValue())) + .findFirst(); + } + + private static boolean hasIdentifier(Supplier> identifierSupplier, String identifierSystem) + { + Objects.requireNonNull(identifierSupplier, "identifierSupplier"); + Objects.requireNonNull(identifierSystem, "identifierSystem"); + + List identifiers = identifierSupplier.get(); + return identifiers == null ? false + : identifiers.stream() + .anyMatch(i -> i.hasSystemElement() && i.getSystemElement().hasValue() && i.hasValueElement() + && i.getValueElement().hasValue() + && identifierSystem.equals(i.getSystemElement().getValue())); } private static Optional findFirst(Optional resource, @@ -75,6 +90,11 @@ public static Optional findFirst(Optional organization Objects.requireNonNull(organization, "organization"); return NamingSystems.findFirst(organization, Organization::getIdentifier, SID); } + + public static boolean hasIdentifier(Organization organization) + { + return organization == null ? false : NamingSystems.hasIdentifier(organization::getIdentifier, SID); + } } public static final class EndpointIdentifier @@ -100,6 +120,11 @@ public static Optional findFirst(Optional endpoint) Objects.requireNonNull(endpoint, "endpoint"); return NamingSystems.findFirst(endpoint, Endpoint::getIdentifier, SID); } + + public static boolean hasIdentifier(Endpoint endpoint) + { + return endpoint == null ? false : NamingSystems.hasIdentifier(endpoint::getIdentifier, SID); + } } public static final class PractitionerIdentifier @@ -125,6 +150,11 @@ public static Optional findFirst(Optional practitioner Objects.requireNonNull(practitioner, "practitioner"); return NamingSystems.findFirst(practitioner, Practitioner::getIdentifier, SID); } + + public static boolean hasIdentifier(Practitioner practitioner) + { + return practitioner == null ? false : NamingSystems.hasIdentifier(practitioner::getIdentifier, SID); + } } public static final class TaskIdentifier @@ -150,5 +180,10 @@ public static Optional findFirst(Optional task) Objects.requireNonNull(task, "task"); return NamingSystems.findFirst(task, Task::getIdentifier, SID); } + + public static boolean hasIdentifier(Task task) + { + return task == null ? false : NamingSystems.hasIdentifier(task::getIdentifier, SID); + } } } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/TargetProvider.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/TargetProvider.java new file mode 100644 index 000000000..8076cf149 --- /dev/null +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/TargetProvider.java @@ -0,0 +1,81 @@ +package dev.dsf.bpe.v2.service; + +import java.util.Arrays; + +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Endpoint; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Organization; +import org.hl7.fhir.r4.model.OrganizationAffiliation; + +import dev.dsf.bpe.v2.constants.CodeSystems.OrganizationRole; +import dev.dsf.bpe.v2.constants.NamingSystems.OrganizationIdentifier; +import dev.dsf.bpe.v2.variables.Targets; + +public interface TargetProvider +{ + interface Builder + { + @FunctionalInterface + interface Predicate + { + /** + * @param affiliation + * not null + * @param member + * not null + * @param endpoint + * not null + * @return true if the entry should part of the resulting {@link Targets} + */ + boolean test(OrganizationAffiliation affiliation, Organization member, Endpoint endpoint); + } + + Targets withCorrelationKey(); + + Targets withoutCorrelationKey(); + + /** + * Returns a builder consisting of the elements that match the given predicate. A null + * predicate will be ignored. + * + * @param predicate + * may be null + * @return filtered builder + */ + Builder filter(Predicate predicate); + } + + Builder create(Identifier parentOrganizationIdentifier); + + default Builder create(String parentOrganizationIdentifierValue) + { + return create(parentOrganizationIdentifierValue == null ? null + : OrganizationIdentifier.withValue(parentOrganizationIdentifierValue)); + } + + Builder create(Identifier parentOrganizationIdentifier, Coding memberOrganizationRole); + + default Builder create(String parentOrganizationIdentifierValue, String memberOrganizationRoleCode) + { + return create( + parentOrganizationIdentifierValue == null ? null + : OrganizationIdentifier.withValue(parentOrganizationIdentifierValue), + memberOrganizationRoleCode == null ? null : OrganizationRole.withCode(memberOrganizationRoleCode)); + } + + Builder create(Identifier parentOrganizationIdentifier, Coding memberOrganizationRole, + Identifier... memberOrganizationIdentifier); + + default Builder create(String parentOrganizationIdentifierValue, String memberOrganizationRoleCode, + String... memberOrganizationIdentifierValue) + { + return create( + parentOrganizationIdentifierValue == null ? null + : OrganizationIdentifier.withValue(parentOrganizationIdentifierValue), + memberOrganizationRoleCode == null ? null : OrganizationRole.withCode(memberOrganizationRoleCode), + memberOrganizationIdentifierValue == null ? null + : Arrays.stream(memberOrganizationIdentifierValue).map(OrganizationIdentifier::withValue) + .toArray(Identifier[]::new)); + } +} diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/variables/Targets.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/variables/Targets.java index 4367140f8..e244013f2 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/variables/Targets.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/variables/Targets.java @@ -49,4 +49,9 @@ public interface Targets * @return true if the entries list is empty */ boolean isEmpty(); + + /** + * @return number of target entries + */ + int size(); } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/PluginV2IntegrationTest.java b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/PluginV2IntegrationTest.java index 33e0448d2..17cb1dd8c 100644 --- a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/PluginV2IntegrationTest.java +++ b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/PluginV2IntegrationTest.java @@ -222,4 +222,10 @@ public void startDsfClientTest() throws Exception executePluginTest(createTestTask("DsfClientTest")); } + + @Test + public void startTargetProviderTest() throws Exception + { + executePluginTest(createTestTask("TargetProviderTest")); + } } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/TargetProviderTest.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/TargetProviderTest.java new file mode 100644 index 000000000..f86397057 --- /dev/null +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/TargetProviderTest.java @@ -0,0 +1,300 @@ +package dev.dsf.bpe.test.service; + +import static dev.dsf.bpe.test.PluginTestExecutor.expectException; +import static dev.dsf.bpe.test.PluginTestExecutor.expectNotNull; +import static dev.dsf.bpe.test.PluginTestExecutor.expectNull; +import static dev.dsf.bpe.test.PluginTestExecutor.expectSame; + +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Identifier; + +import dev.dsf.bpe.test.AbstractTest; +import dev.dsf.bpe.test.PluginTest; +import dev.dsf.bpe.v2.ProcessPluginApi; +import dev.dsf.bpe.v2.activity.ServiceTask; +import dev.dsf.bpe.v2.constants.CodeSystems.OrganizationRole; +import dev.dsf.bpe.v2.constants.NamingSystems.OrganizationIdentifier; +import dev.dsf.bpe.v2.error.ErrorBoundaryEvent; +import dev.dsf.bpe.v2.service.TargetProvider; +import dev.dsf.bpe.v2.variables.Target; +import dev.dsf.bpe.v2.variables.Targets; +import dev.dsf.bpe.v2.variables.Variables; + +public class TargetProviderTest extends AbstractTest implements ServiceTask +{ + private static final String ORGANIZATION_IDENTIFIER_PARENT_VALUE = "Parent_Organization"; + private static final Identifier ORGANIZATION_IDENTIFIER_PARENT = OrganizationIdentifier + .withValue(ORGANIZATION_IDENTIFIER_PARENT_VALUE); + + private static final String ORGANIZATION_IDENTIFIER_LOCAL_VALUE = "Test_Organization"; + private static final Identifier ORGANIZATION_IDENTIFIER_LOCAL = OrganizationIdentifier + .withValue(ORGANIZATION_IDENTIFIER_LOCAL_VALUE); + private static final String ORGANIZATION_IDENTIFIER_EXTERNAL_VALUE = "External_Test_Organization"; + private static final Identifier ORGANIZATION_IDENTIFIER_EXTERNAL = OrganizationIdentifier + .withValue(ORGANIZATION_IDENTIFIER_EXTERNAL_VALUE); + + private static final String ENDPOINT_IDENTIFIER_LOCAL_VALUE = "Test_Endpoint"; + private static final String ENDPOINT_IDENTIFIER_EXTERNAL_VALUE = "External_Test_Endpoint"; + + private static final String ORGANIZATION_IDENTIFIER_NOT_EXISTING_VALUE = "not-existing-identifier-value"; + private static final Identifier ORGANIZATION_IDENTIFIER_NOT_EXISTING = OrganizationIdentifier + .withValue(ORGANIZATION_IDENTIFIER_NOT_EXISTING_VALUE); + private static final String MEMBER_ROLE_NOT_EXISTING_CODE = "not-existing-role"; + private static final Coding MEMBER_ROLE_NOT_EXISTING = OrganizationRole.withCode(MEMBER_ROLE_NOT_EXISTING_CODE); + + @Override + public void execute(ProcessPluginApi api, Variables variables) throws ErrorBoundaryEvent, Exception + { + executeTests(api, variables, api.getTargetProvider()); + } + + @PluginTest + public void createForExistingParentWithCorrelationKey(TargetProvider targetProvider) throws Exception + { + Targets targets = targetProvider.create(ORGANIZATION_IDENTIFIER_PARENT_VALUE).withCorrelationKey(); + expectNotNull(targets); + expectSame(2, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(2, entries.size()); + + Target target0 = entries.get(0); + expectNotNull(target0); + expectSame(ORGANIZATION_IDENTIFIER_LOCAL_VALUE, target0.getOrganizationIdentifierValue()); + expectSame(ENDPOINT_IDENTIFIER_LOCAL_VALUE, target0.getEndpointIdentifierValue()); + expectNotNull(target0.getEndpointUrl()); + expectNotNull(target0.getCorrelationKey()); + + Target target1 = entries.get(1); + expectNotNull(target1); + expectSame(ORGANIZATION_IDENTIFIER_EXTERNAL_VALUE, target1.getOrganizationIdentifierValue()); + expectSame(ENDPOINT_IDENTIFIER_EXTERNAL_VALUE, target1.getEndpointIdentifierValue()); + expectNotNull(target1.getEndpointUrl()); + expectNotNull(target1.getCorrelationKey()); + } + + @PluginTest + public void createForExistingParentWithoutCorrelationKey(TargetProvider targetProvider) throws Exception + { + Targets targets = targetProvider.create(ORGANIZATION_IDENTIFIER_PARENT_VALUE).withoutCorrelationKey(); + expectNotNull(targets); + expectSame(2, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(2, entries.size()); + + Target target0 = entries.get(0); + expectNotNull(target0); + expectSame(ORGANIZATION_IDENTIFIER_LOCAL_VALUE, target0.getOrganizationIdentifierValue()); + expectSame(ENDPOINT_IDENTIFIER_LOCAL_VALUE, target0.getEndpointIdentifierValue()); + expectNotNull(target0.getEndpointUrl()); + expectNull(target0.getCorrelationKey()); + + Target target1 = entries.get(1); + expectNotNull(target1); + expectSame(ORGANIZATION_IDENTIFIER_EXTERNAL_VALUE, target1.getOrganizationIdentifierValue()); + expectSame(ENDPOINT_IDENTIFIER_EXTERNAL_VALUE, target1.getEndpointIdentifierValue()); + expectNotNull(target1.getEndpointUrl()); + expectNull(target1.getCorrelationKey()); + } + + @PluginTest + public void createForExistingParentWithFilter(TargetProvider targetProvider) throws Exception + { + AtomicInteger counter = new AtomicInteger(0); + + Targets targets = targetProvider.create(ORGANIZATION_IDENTIFIER_PARENT_VALUE).filter((a, o, e) -> + { + expectNotNull(a); + expectNotNull(o); + expectNotNull(e); + + counter.incrementAndGet(); + + return false; + + }).withCorrelationKey(); + + expectNotNull(targets); + expectSame(0, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(0, entries.size()); + + expectSame(2, counter.get()); + } + + @PluginTest + public void createForNotExistingParentIdentifierValue(TargetProvider targetProvider) throws Exception + { + Targets targets = targetProvider.create(ORGANIZATION_IDENTIFIER_NOT_EXISTING_VALUE).withCorrelationKey(); + expectNotNull(targets); + expectSame(0, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(0, entries.size()); + } + + @PluginTest + public void createForNotExistingParentIdentifier(TargetProvider targetProvider) throws Exception + { + Targets targets = targetProvider.create(ORGANIZATION_IDENTIFIER_NOT_EXISTING).withCorrelationKey(); + expectNotNull(targets); + expectSame(0, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(0, entries.size()); + } + + @PluginTest + public void createForExistingParentWithCorrelationKeyDic(TargetProvider targetProvider) throws Exception + { + Targets targets = targetProvider.create(ORGANIZATION_IDENTIFIER_PARENT, OrganizationRole.dic()) + .withCorrelationKey(); + expectNotNull(targets); + expectSame(1, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(1, entries.size()); + + Target target0 = entries.get(0); + expectNotNull(target0); + expectSame(ORGANIZATION_IDENTIFIER_LOCAL_VALUE, target0.getOrganizationIdentifierValue()); + expectSame(ENDPOINT_IDENTIFIER_LOCAL_VALUE, target0.getEndpointIdentifierValue()); + expectNotNull(target0.getEndpointUrl()); + expectNotNull(target0.getCorrelationKey()); + } + + @PluginTest + public void createForExistingParentWithCorrelationKeyDts(TargetProvider targetProvider) throws Exception + { + Targets targets = targetProvider.create(ORGANIZATION_IDENTIFIER_PARENT, OrganizationRole.dts()) + .withCorrelationKey(); + expectNotNull(targets); + expectSame(1, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(1, entries.size()); + + Target target0 = entries.get(0); + expectNotNull(target0); + expectSame(ORGANIZATION_IDENTIFIER_EXTERNAL_VALUE, target0.getOrganizationIdentifierValue()); + expectSame(ENDPOINT_IDENTIFIER_EXTERNAL_VALUE, target0.getEndpointIdentifierValue()); + expectNotNull(target0.getEndpointUrl()); + expectNotNull(target0.getCorrelationKey()); + } + + @PluginTest + public void createForExistingParentWithCorrelationKeyDicMemberIdentifier(TargetProvider targetProvider) + throws Exception + { + Targets targets = targetProvider + .create(ORGANIZATION_IDENTIFIER_PARENT, OrganizationRole.dic(), ORGANIZATION_IDENTIFIER_LOCAL) + .withCorrelationKey(); + expectNotNull(targets); + expectSame(1, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(1, entries.size()); + + Target target0 = entries.get(0); + expectNotNull(target0); + expectSame(ORGANIZATION_IDENTIFIER_LOCAL_VALUE, target0.getOrganizationIdentifierValue()); + expectSame(ENDPOINT_IDENTIFIER_LOCAL_VALUE, target0.getEndpointIdentifierValue()); + expectNotNull(target0.getEndpointUrl()); + expectNotNull(target0.getCorrelationKey()); + } + + @PluginTest + public void createForExistingParentWithCorrelationKeyDicMemberIdentifierWithoutDicRole( + TargetProvider targetProvider) throws Exception + { + Targets targets = targetProvider + .create(ORGANIZATION_IDENTIFIER_PARENT, OrganizationRole.dic(), ORGANIZATION_IDENTIFIER_EXTERNAL) + .withCorrelationKey(); + expectNotNull(targets); + expectSame(0, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(0, entries.size()); + } + + @PluginTest + public void createNull(TargetProvider targetProvider) throws Exception + { + expectException(NullPointerException.class, () -> targetProvider.create((String) null).withoutCorrelationKey()); + expectException(NullPointerException.class, + () -> targetProvider.create((Identifier) null).withoutCorrelationKey()); + } + + @PluginTest + public void createNotNullNull(TargetProvider targetProvider) throws Exception + { + expectException(NullPointerException.class, () -> targetProvider + .create(ORGANIZATION_IDENTIFIER_PARENT_VALUE, (String) null).withoutCorrelationKey()); + expectException(NullPointerException.class, + () -> targetProvider.create(ORGANIZATION_IDENTIFIER_PARENT, (Coding) null).withoutCorrelationKey()); + } + + @PluginTest + public void createNotNullNotNullNull(TargetProvider targetProvider) throws Exception + { + expectException(NullPointerException.class, + () -> targetProvider + .create(ORGANIZATION_IDENTIFIER_PARENT_VALUE, MEMBER_ROLE_NOT_EXISTING_CODE, (String[]) null) + .withoutCorrelationKey()); + expectException(NullPointerException.class, + () -> targetProvider + .create(ORGANIZATION_IDENTIFIER_PARENT, MEMBER_ROLE_NOT_EXISTING, (Identifier[]) null) + .withoutCorrelationKey()); + } + + @PluginTest + public void createForExistingParentWithCorrelationKeyNullFilter(TargetProvider targetProvider) throws Exception + { + Targets targets = targetProvider.create(ORGANIZATION_IDENTIFIER_PARENT_VALUE).filter(null).withCorrelationKey(); + expectNotNull(targets); + expectSame(2, targets.size()); + + List entries = targets.getEntries().stream() + .sorted(Comparator.comparing(Target::getOrganizationIdentifierValue).reversed()).toList(); + expectNotNull(entries); + expectSame(2, entries.size()); + + Target target0 = entries.get(0); + expectNotNull(target0); + expectSame(ORGANIZATION_IDENTIFIER_LOCAL_VALUE, target0.getOrganizationIdentifierValue()); + expectSame(ENDPOINT_IDENTIFIER_LOCAL_VALUE, target0.getEndpointIdentifierValue()); + expectNotNull(target0.getEndpointUrl()); + expectNotNull(target0.getCorrelationKey()); + + Target target1 = entries.get(1); + expectNotNull(target1); + expectSame(ORGANIZATION_IDENTIFIER_EXTERNAL_VALUE, target1.getOrganizationIdentifierValue()); + expectSame(ENDPOINT_IDENTIFIER_EXTERNAL_VALUE, target1.getEndpointIdentifierValue()); + expectNotNull(target1.getEndpointUrl()); + expectNotNull(target1.getCorrelationKey()); + } +} diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/spring/config/Config.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/spring/config/Config.java index 0b610cffe..804516e9b 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/spring/config/Config.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/spring/config/Config.java @@ -33,6 +33,7 @@ import dev.dsf.bpe.test.service.MimetypeServiceTest; import dev.dsf.bpe.test.service.OrganizationProviderTest; import dev.dsf.bpe.test.service.ProxyTest; +import dev.dsf.bpe.test.service.TargetProviderTest; import dev.dsf.bpe.test.service.TestActivitySelector; import dev.dsf.bpe.v2.documentation.ProcessDocumentation; import dev.dsf.bpe.v2.fhir.FhirResourceModifier; @@ -68,7 +69,7 @@ public static ActivityPrototypeBeanCreator activityPrototypeBeanCreator() ExceptionTest.class, ContinueSendTest.class, ContinueSendTestSend.class, ContinueSendTestEvaluate.class, JsonVariableTestSet.class, JsonVariableTestGet.class, CryptoServiceTest.class, MimetypeServiceTest.class, FhirBinaryVariableTestSet.class, FhirBinaryVariableTestGet.class, - DsfClientTest.class); + DsfClientTest.class, TargetProviderTest.class); } @Bean diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/bpe/test.bpmn b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/bpe/test.bpmn index ec8bdb9d5..9c062e6b1 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/bpe/test.bpmn +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/resources/bpe/test.bpmn @@ -38,6 +38,7 @@ Flow_151zxir Flow_1bo772x Flow_1gipugc + Flow_1wjdaz2 @@ -64,6 +65,7 @@ Flow_1ic3b4h Flow_01nnroq Flow_04z1f6i + Flow_0w963xd Flow_0a1kwg9 @@ -290,6 +292,14 @@ ${testActivity == 'DsfClientTest'} + + Flow_1wjdaz2 + Flow_0w963xd + + + + ${testActivity == 'TargetProviderTest'} + @@ -413,6 +423,10 @@ + + + + @@ -634,6 +648,16 @@ + + + + + + + + + + From fa14db561ba47b67dc74e8a30460b869c0c431c3 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 28 May 2025 13:33:29 +0200 Subject: [PATCH 3/4] javadoc --- .../dsf/bpe/v2/service/TargetProvider.java | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/TargetProvider.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/TargetProvider.java index 8076cf149..ef5905537 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/TargetProvider.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/TargetProvider.java @@ -8,8 +8,10 @@ import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.OrganizationAffiliation; +import dev.dsf.bpe.v2.constants.BpmnExecutionVariables; import dev.dsf.bpe.v2.constants.CodeSystems.OrganizationRole; import dev.dsf.bpe.v2.constants.NamingSystems.OrganizationIdentifier; +import dev.dsf.bpe.v2.variables.Target; import dev.dsf.bpe.v2.variables.Targets; public interface TargetProvider @@ -31,8 +33,31 @@ interface Predicate boolean test(OrganizationAffiliation affiliation, Organization member, Endpoint endpoint); } + /** + * A correlationKey should be used if return messages i.e. Task resources from multiple organizations + * with the same message-name are expected in a following multi instance message receive task or intermediate + * message catch event in a multi instance subprocess.
+ * Note: The correlationKey needs to be set as a {@link BpmnExecutionVariables#CORRELATION_KEY} variable in the + * message receive task or intermediate message catch event of a subprocess before incoming messages i.e. Task + * resources can be correlated. Within a BPMN file this can be accomplished by setting an input variable with + * name: {@link BpmnExecutionVariables#CORRELATION_KEY}, type:
string or expression, and + * value: ${target.correlationKey}. + *

+ * A correlationKey should also be used when sending a message i.e. Task resource back to an + * organization waiting for multiple returns. + * + * @return {@link Targets} including correlation keys + * @see Target#getCorrelationKey() + */ Targets withCorrelationKey(); + /** + * {@link Targets} without correlation key can be used when sending out multiple messages without expecting + * replies. + * + * @return {@link Targets} without correlation keys + * @see Target#getCorrelationKey() + */ Targets withoutCorrelationKey(); /** @@ -46,16 +71,40 @@ interface Predicate Builder filter(Predicate predicate); } + /** + * @param parentOrganizationIdentifier + * not null + * @return {@link Targets} builder for all active members of the given parent organization + */ Builder create(Identifier parentOrganizationIdentifier); + /** + * @param parentOrganizationIdentifierValue + * not null + * @return {@link Targets} builder for all active members of the given parent organization + */ default Builder create(String parentOrganizationIdentifierValue) { return create(parentOrganizationIdentifierValue == null ? null : OrganizationIdentifier.withValue(parentOrganizationIdentifierValue)); } + /** + * @param parentOrganizationIdentifier + * not null + * @param memberOrganizationRole + * not null + * @return {@link Targets} builder for all active members of the given parent organization with the given role + */ Builder create(Identifier parentOrganizationIdentifier, Coding memberOrganizationRole); + /** + * @param parentOrganizationIdentifierValue + * not null + * @param memberOrganizationRoleCode + * not null + * @return {@link Targets} builder for all active members of the given parent organization with the given role + */ default Builder create(String parentOrganizationIdentifierValue, String memberOrganizationRoleCode) { return create( @@ -64,9 +113,29 @@ default Builder create(String parentOrganizationIdentifierValue, String memberOr memberOrganizationRoleCode == null ? null : OrganizationRole.withCode(memberOrganizationRoleCode)); } + /** + * @param parentOrganizationIdentifier + * not null + * @param memberOrganizationRole + * not null + * @param memberOrganizationIdentifier + * not null, array null values will be ignored + * @return {@link Targets} builder for all active members of the given parent organization with the given role, + * filtered by the given member organization + */ Builder create(Identifier parentOrganizationIdentifier, Coding memberOrganizationRole, Identifier... memberOrganizationIdentifier); + /** + * @param parentOrganizationIdentifierValue + * not null + * @param memberOrganizationRoleCode + * not null + * @param memberOrganizationIdentifierValue + * not null, array null values will be ignored + * @return {@link Targets} builder for all active members of the given parent organization with the given role, + * filtered by the given member organization + */ default Builder create(String parentOrganizationIdentifierValue, String memberOrganizationRoleCode, String... memberOrganizationIdentifierValue) { From b2985503a9faef175852828ca8b6d02b41158fd3 Mon Sep 17 00:00:00 2001 From: Hauke Hund Date: Wed, 28 May 2025 13:34:09 +0200 Subject: [PATCH 4/4] added method to get the first element of the entries list --- .../main/java/dev/dsf/bpe/v2/variables/TargetsImpl.java | 7 +++++++ .../src/main/java/dev/dsf/bpe/v2/variables/Targets.java | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/variables/TargetsImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/variables/TargetsImpl.java index 7a7af97ce..7d1857937 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/variables/TargetsImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/variables/TargetsImpl.java @@ -4,6 +4,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonCreator; @@ -72,6 +73,12 @@ public int size() return entries.size(); } + @Override + public Optional getFirst() + { + return entries.isEmpty() ? Optional.empty() : Optional.of(entries.get(0)); + } + @Override public String toString() { diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/variables/Targets.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/variables/Targets.java index e244013f2..f65aba713 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/variables/Targets.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/variables/Targets.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.List; +import java.util.Optional; import dev.dsf.bpe.v2.constants.BpmnExecutionVariables; @@ -54,4 +55,10 @@ public interface Targets * @return number of target entries */ int size(); + + /** + * @return {@link Optional} with the first element of the target entries, or {@link Optional#empty()} if + * {@link Targets#isEmpty()} + */ + Optional getFirst(); } \ No newline at end of file