diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiFactory.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiFactory.java index a103a0394..49e7d557e 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiFactory.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/ProcessPluginApiFactory.java @@ -24,12 +24,12 @@ import ca.uhn.fhir.context.FhirContext; import dev.dsf.bpe.v2.config.ProxyConfig; +import dev.dsf.bpe.v2.service.ClientConfigProvider; import dev.dsf.bpe.v2.service.CompressionService; import dev.dsf.bpe.v2.service.CryptoService; import dev.dsf.bpe.v2.service.DataLogger; import dev.dsf.bpe.v2.service.DsfClientProvider; import dev.dsf.bpe.v2.service.EndpointProvider; -import dev.dsf.bpe.v2.service.FhirClientConfigProvider; import dev.dsf.bpe.v2.service.FhirClientProvider; import dev.dsf.bpe.v2.service.MailService; import dev.dsf.bpe.v2.service.MimeTypeService; @@ -63,7 +63,7 @@ public ProcessPluginApi get() { return new ProcessPluginApiImpl(processPluginDefinition, fromParent(ProxyConfig.class), fromParent(EndpointProvider.class), fromParent(FhirContext.class), fromParent(DsfClientProvider.class), - fromParent(FhirClientProvider.class), fromParent(FhirClientConfigProvider.class), + fromParent(FhirClientProvider.class), fromParent(ClientConfigProvider.class), fromParent(OidcClientProvider.class), fromParent(MailService.class), fromParent(MimeTypeService.class), fromParent(ObjectMapper.class), fromParent(OrganizationProvider.class), fromParent(ProcessAuthorizationHelper.class), fromParent(QuestionnaireResponseHelper.class), @@ -71,4 +71,4 @@ public ProcessPluginApi get() fromParent(CryptoService.class), fromParent(TargetProvider.class), fromParent(DataLogger.class), fromParent(ValidationServiceProvider.class)); } -} \ No newline at end of file +} 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 ebe47415a..62d20f43a 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 @@ -23,12 +23,12 @@ import ca.uhn.fhir.context.FhirContext; import dev.dsf.bpe.v2.config.ProxyConfig; +import dev.dsf.bpe.v2.service.ClientConfigProvider; import dev.dsf.bpe.v2.service.CompressionService; import dev.dsf.bpe.v2.service.CryptoService; import dev.dsf.bpe.v2.service.DataLogger; import dev.dsf.bpe.v2.service.DsfClientProvider; import dev.dsf.bpe.v2.service.EndpointProvider; -import dev.dsf.bpe.v2.service.FhirClientConfigProvider; import dev.dsf.bpe.v2.service.FhirClientProvider; import dev.dsf.bpe.v2.service.MailService; import dev.dsf.bpe.v2.service.MimeTypeService; @@ -49,7 +49,7 @@ public class ProcessPluginApiImpl implements ProcessPluginApi, InitializingBean private final FhirContext fhirContext; private final DsfClientProvider dsfClientProvider; private final FhirClientProvider fhirClientProvider; - private final FhirClientConfigProvider fhirClientConfigProvider; + private final ClientConfigProvider fhirClientConfigProvider; private final OidcClientProvider oidcClientProvider; private final MailService mailService; private final MimeTypeService mimeTypeService; @@ -67,7 +67,7 @@ public class ProcessPluginApiImpl implements ProcessPluginApi, InitializingBean public ProcessPluginApiImpl(ProcessPluginDefinition processPluginDefinition, ProxyConfig proxyConfig, EndpointProvider endpointProvider, FhirContext fhirContext, DsfClientProvider dsfClientProvider, - FhirClientProvider fhirClientProvider, FhirClientConfigProvider fhirClientConfigProvider, + FhirClientProvider fhirClientProvider, ClientConfigProvider fhirClientConfigProvider, OidcClientProvider oidcClientProvider, MailService mailService, MimeTypeService mimeTypeService, ObjectMapper objectMapper, OrganizationProvider organizationProvider, ProcessAuthorizationHelper processAuthorizationHelper, @@ -161,7 +161,7 @@ public FhirClientProvider getFhirClientProvider() } @Override - public FhirClientConfigProvider getFhirClientConfigProvider() + public ClientConfigProvider getFhirClientConfigProvider() { return fhirClientConfigProvider; } diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/AbstractDsfClientJerseyWithRetry.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/AbstractDsfClientJerseyWithRetry.java index e0dd931de..c90ce5115 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/AbstractDsfClientJerseyWithRetry.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/AbstractDsfClientJerseyWithRetry.java @@ -34,18 +34,20 @@ public abstract class AbstractDsfClientJerseyWithRetry protected final DsfClientJersey delegate; private final int nTimes; - private final Duration delay; + private final DelayStrategy delayStrategy; - protected AbstractDsfClientJerseyWithRetry(DsfClientJersey delegate, int nTimes, Duration delay) + protected AbstractDsfClientJerseyWithRetry(DsfClientJersey delegate, int nTimes, DelayStrategy delayStrategy) { this.delegate = delegate; this.nTimes = nTimes; - this.delay = delay; + this.delayStrategy = delayStrategy; } protected final R retry(Supplier supplier) { RuntimeException caughtException = null; + Duration delay = delayStrategy.getFirstDelay(); + for (int tryNumber = 0; tryNumber <= nTimes || nTimes == RetryClient.RETRY_FOREVER; tryNumber++) { try @@ -63,8 +65,11 @@ else if (nTimes != RetryClient.RETRY_FOREVER) { if (tryNumber < nTimes || nTimes == RetryClient.RETRY_FOREVER) { - logger.warn("Caught {} - {}; trying again in {}s{}", e.getClass(), e.getMessage(), - delay.toSeconds(), + if (tryNumber > 0) + delay = delayStrategy.getNextDelay(delay); + + logger.warn("Caught {} - {}; trying again in {}{}", e.getClass(), e.getMessage(), + delay.toString(), nTimes == RetryClient.RETRY_FOREVER ? " (retry " + (tryNumber + 1) + ")" : ""); try diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/BasicDsfClientWithRetryImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/BasicDsfClientWithRetryImpl.java index 8416b70d5..3053280ff 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/BasicDsfClientWithRetryImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/BasicDsfClientWithRetryImpl.java @@ -16,7 +16,6 @@ package dev.dsf.bpe.v2.client.dsf; import java.io.InputStream; -import java.time.Duration; import java.util.List; import java.util.Map; @@ -24,6 +23,7 @@ import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.CapabilityStatement; import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r4.model.StructureDefinition; @@ -31,9 +31,9 @@ class BasicDsfClientWithRetryImpl extends AbstractDsfClientJerseyWithRetry implements BasicDsfClient { - BasicDsfClientWithRetryImpl(DsfClientJersey delegate, int nTimes, Duration delay) + BasicDsfClientWithRetryImpl(DsfClientJersey delegate, int nTimes, DelayStrategy delayStrategy) { - super(delegate, nTimes, delay); + super(delegate, nTimes, delayStrategy); } @Override @@ -218,4 +218,31 @@ public Bundle history(Class resourceType, String id, int pag { return retry(() -> delegate.history(resourceType, id, page, count)); } + + @Override + public R operation(String operationName, Parameters parameters, Class returnType) + { + return retry(() -> delegate.operation(operationName, parameters, returnType)); + } + + @Override + public R operation(Class resourceType, String operationName, + Parameters parameters, Class returnType) + { + return retry(() -> delegate.operation(resourceType, operationName, parameters, returnType)); + } + + @Override + public R operation(Class resourceType, String id, String operationName, + Parameters parameters, Class returnType) + { + return retry(() -> delegate.operation(resourceType, id, operationName, parameters, returnType)); + } + + @Override + public R operation(Class resourceType, String id, String version, + String operationName, Parameters parameters, Class returnType) + { + return retry(() -> delegate.operation(resourceType, id, version, operationName, parameters, returnType)); + } } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/DsfClientJersey.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/DsfClientJersey.java index 5cf1914fb..3ca617de2 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/DsfClientJersey.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/DsfClientJersey.java @@ -15,18 +15,28 @@ */ package dev.dsf.bpe.v2.client.dsf; +import java.io.IOException; import java.io.InputStream; import java.security.KeyStore; import java.text.SimpleDateFormat; import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Optional; import java.util.TimeZone; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.function.Supplier; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -40,10 +50,14 @@ import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientProperties; +import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature; import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.logging.LoggingFeature.Verbosity; import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Bundle.BundleEntryResponseComponent; +import org.hl7.fhir.r4.model.Bundle.BundleType; import org.hl7.fhir.r4.model.CapabilityStatement; import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.OperationOutcome; @@ -60,15 +74,24 @@ import ca.uhn.fhir.model.api.annotation.ResourceDef; import ca.uhn.fhir.rest.api.Constants; import dev.dsf.bpe.v2.client.dsf.BinaryInputStream.Range; +import dev.dsf.bpe.v2.client.fhir.ClientConfig.BasicAuthentication; +import dev.dsf.bpe.v2.client.fhir.ClientConfig.BearerAuthentication; +import dev.dsf.bpe.v2.client.fhir.ClientConfig.OidcAuthentication; +import dev.dsf.bpe.v2.service.OidcClientProvider; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.WebApplicationException; +import jakarta.ws.rs.client.AsyncInvoker; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.ClientRequestContext; import jakarta.ws.rs.client.ClientRequestFilter; import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Invocation.Builder; +import jakarta.ws.rs.client.InvocationCallback; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.EntityTag; +import jakarta.ws.rs.core.Feature; +import jakarta.ws.rs.core.FeatureContext; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; @@ -93,6 +116,8 @@ public class DsfClientJersey implements DsfClient private static final String CONTENT_RANGE_PATTERN_TEXT = "bytes (?\\d+)-(?\\d+)\\/(?\\d+)"; private static final Pattern CONTENT_RANGE_PATTERN = Pattern.compile(CONTENT_RANGE_PATTERN_TEXT); + private static final Pattern HTTP_STATUS_PATTERN = Pattern.compile("^[12345][0-9]{2}(?: |$)"); + private static Class getFhirClass(ResourceType type) { try @@ -105,17 +130,90 @@ private static Class getFhirClass(ResourceType type) } } + private static final class BearerAuthenticationFeature implements Feature + { + final Supplier tokenProvider; + + BearerAuthenticationFeature(Supplier tokenProvider) + { + this.tokenProvider = Objects.requireNonNull(tokenProvider, "tokenProvider"); + } + + @Override + public boolean configure(FeatureContext context) + { + context.register(new ClientRequestFilter() + { + @Override + public void filter(ClientRequestContext requestContext) throws IOException + { + requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, + Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER + String.valueOf(tokenProvider.get())); + } + }); + + return true; + } + } + private final Client client; private final String baseUrl; private final PreferReturnMinimalWithRetry preferReturnMinimal; private final PreferReturnOutcomeWithRetry preferReturnOutcome; - public DsfClientJersey(String baseUrl, KeyStore trustStore, KeyStore keyStore, char[] keyStorePassword, - String proxySchemeHostPort, String proxyUserName, char[] proxyPassword, Duration connectTimeout, - Duration readTimeout, boolean logRequestsAndResponses, String userAgentValue, FhirContext fhirContext, + private final ScheduledExecutorService scheduler; + + public DsfClientJersey(ScheduledExecutorService scheduler, dev.dsf.bpe.v2.client.fhir.ClientConfig clientConfig, + OidcClientProvider oidcClientProvider, String userAgent, FhirContext fhirContext, ReferenceCleaner referenceCleaner) { + this(scheduler, clientConfig.getBaseUrl(), clientConfig.getTrustStore(), + clientConfig.getCertificateAuthentication() == null ? null + : clientConfig.getCertificateAuthentication().getKeyStore(), + clientConfig.getCertificateAuthentication() == null ? null + : clientConfig.getCertificateAuthentication().getKeyStorePassword(), + clientConfig.getProxy() == null ? null : clientConfig.getProxy().getUrl(), + clientConfig.getProxy() == null ? null : clientConfig.getProxy().getUsername(), + clientConfig.getProxy() == null ? null : clientConfig.getProxy().getPassword(), + clientConfig.getConnectTimeout(), clientConfig.getReadTimeout(), clientConfig.isDebugLoggingEnabled(), + userAgent, fhirContext, referenceCleaner, authFeatures(clientConfig, oidcClientProvider)); + } + + private static Stream authFeatures(dev.dsf.bpe.v2.client.fhir.ClientConfig clientConfig, + OidcClientProvider oidcClientProvider) + { + BasicAuthentication basicAuth = clientConfig.getBasicAuthentication(); + Feature basicAuthFeature = basicAuth == null ? null + : HttpAuthenticationFeature.basic(basicAuth.getUsername(), new String(basicAuth.getPassword())); + + BearerAuthentication bearerAuth = clientConfig.getBearerAuthentication(); + Feature bearerAuthFeature = bearerAuth == null ? null : new BearerAuthenticationFeature(bearerAuth::getToken); + + OidcAuthentication oidcAuth = clientConfig.getOidcAuthentication(); + Feature oidcAuthFeature = oidcAuth == null ? null + : new BearerAuthenticationFeature(oidcClientProvider.getOidcClient(oidcAuth)::getAccessToken); + + return Stream.of(basicAuthFeature, bearerAuthFeature, oidcAuthFeature).filter(Objects::nonNull); + } + + public DsfClientJersey(ScheduledExecutorService scheduler, String baseUrl, KeyStore trustStore, KeyStore keyStore, + char[] keyStorePassword, String proxySchemeHostPort, String proxyUserName, char[] proxyPassword, + Duration connectTimeout, Duration readTimeout, boolean logRequestsAndResponses, String userAgentValue, + FhirContext fhirContext, ReferenceCleaner referenceCleaner) + { + this(scheduler, baseUrl, trustStore, keyStore, keyStorePassword, proxySchemeHostPort, proxyUserName, + proxyPassword, connectTimeout, readTimeout, logRequestsAndResponses, userAgentValue, fhirContext, + referenceCleaner, Stream.of()); + } + + private DsfClientJersey(ScheduledExecutorService scheduler, String baseUrl, KeyStore trustStore, KeyStore keyStore, + char[] keyStorePassword, String proxySchemeHostPort, String proxyUserName, char[] proxyPassword, + Duration connectTimeout, Duration readTimeout, boolean logRequestsAndResponses, String userAgentValue, + FhirContext fhirContext, ReferenceCleaner referenceCleaner, Stream authFeatures) + { + this.scheduler = scheduler; + SSLContext sslContext = null; if (trustStore != null && keyStore == null && keyStorePassword == null) sslContext = SslConfigurator.newInstance().trustStore(trustStore).createSSLContext(); @@ -125,28 +223,30 @@ else if (trustStore != null && keyStore != null && keyStorePassword != null) ClientBuilder builder = ClientBuilder.newBuilder(); + authFeatures.forEach(builder::register); + if (sslContext != null) - builder = builder.sslContext(sslContext); + builder.sslContext(sslContext); ClientConfig config = new ClientConfig(); config.connectorProvider(new ApacheConnectorProvider()); config.property(ClientProperties.PROXY_URI, proxySchemeHostPort); config.property(ClientProperties.PROXY_USERNAME, proxyUserName); config.property(ClientProperties.PROXY_PASSWORD, proxyPassword == null ? null : String.valueOf(proxyPassword)); - builder = builder.withConfig(config); + builder.withConfig(config); if (userAgentValue != null && !userAgentValue.isBlank()) - builder = builder.register((ClientRequestFilter) requestContext -> requestContext.getHeaders() + builder.register((ClientRequestFilter) requestContext -> requestContext.getHeaders() .add(HttpHeaders.USER_AGENT, userAgentValue)); - builder = builder.readTimeout(readTimeout.toMillis(), TimeUnit.MILLISECONDS) - .connectTimeout(connectTimeout.toMillis(), TimeUnit.MILLISECONDS); + builder.readTimeout(readTimeout.toMillis(), TimeUnit.MILLISECONDS).connectTimeout(connectTimeout.toMillis(), + TimeUnit.MILLISECONDS); - builder = builder.register(new FhirAdapter(fhirContext, referenceCleaner)); + builder.register(new FhirAdapter(fhirContext, referenceCleaner)); if (logRequestsAndResponses) { - builder = builder.register(new LoggingFeature(requestDebugLogger, Level.INFO, Verbosity.PAYLOAD_ANY, + builder.register(new LoggingFeature(requestDebugLogger, Level.INFO, Verbosity.PAYLOAD_ANY, LoggingFeature.DEFAULT_MAX_ENTITY_SIZE)); } @@ -208,14 +308,26 @@ private void logStatusAndHeaders(Response response) logger.debug("HTTP header Last-Modified: {}", response.getHeaderString(HttpHeaders.LAST_MODIFIED)); } - private PreferReturn toPreferReturn(PreferReturnType returnType, Class resourceType, + private PreferReturn toPreferReturn(PreferReturnType returnType, Class resourceType, Response response) { return switch (returnType) { case REPRESENTATION -> PreferReturn.resource(response.readEntity(resourceType)); - case MINIMAL -> PreferReturn.minimal(response.getLocation()); + + case MINIMAL -> { + response.close(); + + String location = response.getLocation() == null ? null : response.getLocation().toString(); + + if (location == null) + location = response.getHeaderString(HttpHeaders.CONTENT_LOCATION); + + yield PreferReturn.minimal(location); + } + case OPERATION_OUTCOME -> PreferReturn.outcome(response.readEntity(OperationOutcome.class)); + default -> throw new RuntimeException(PreferReturn.class.getName() + " value " + returnType + " not supported"); }; @@ -233,52 +345,55 @@ public PreferReturnOutcomeWithRetry withOperationOutcomeReturn() return preferReturnOutcome; } - PreferReturn create(PreferReturnType returnType, Resource resource) + PreferReturn create(PreferReturnType preferReturnType, Class returnType, R resource) { + Objects.requireNonNull(preferReturnType, "preferReturnType"); Objects.requireNonNull(returnType, "returnType"); Objects.requireNonNull(resource, "resource"); Response response = getResource().path(resource.getClass().getAnnotation(ResourceDef.class).name()).request() - .header(Constants.HEADER_PREFER, returnType.getHeaderValue()).accept(Constants.CT_FHIR_JSON_NEW) + .header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()).accept(Constants.CT_FHIR_JSON_NEW) .post(Entity.entity(resource, Constants.CT_FHIR_JSON_NEW)); logStatusAndHeaders(response); if (Status.CREATED.getStatusCode() == response.getStatus()) - return toPreferReturn(returnType, resource.getClass(), response); + return toPreferReturn(preferReturnType, returnType, response); else throw handleError(response); } - PreferReturn createConditionaly(PreferReturnType returnType, Resource resource, String ifNoneExistCriteria) + PreferReturn createConditionaly(PreferReturnType preferReturnType, Class returnType, + R resource, String ifNoneExistCriteria) { + Objects.requireNonNull(preferReturnType, "preferReturnType"); Objects.requireNonNull(returnType, "returnType"); Objects.requireNonNull(resource, "resource"); Objects.requireNonNull(ifNoneExistCriteria, "ifNoneExistCriteria"); Response response = getResource().path(resource.getClass().getAnnotation(ResourceDef.class).name()).request() - .header(Constants.HEADER_PREFER, returnType.getHeaderValue()) + .header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()) .header(Constants.HEADER_IF_NONE_EXIST, ifNoneExistCriteria).accept(Constants.CT_FHIR_JSON_NEW) .post(Entity.entity(resource, Constants.CT_FHIR_JSON_NEW)); logStatusAndHeaders(response); if (Status.CREATED.getStatusCode() == response.getStatus()) - return toPreferReturn(returnType, resource.getClass(), response); + return toPreferReturn(preferReturnType, returnType, response); else throw handleError(response); } - PreferReturn createBinary(PreferReturnType returnType, InputStream in, MediaType mediaType, + PreferReturn createBinary(PreferReturnType preferReturnType, InputStream in, MediaType mediaType, String securityContextReference) { - Objects.requireNonNull(returnType, "returnType"); + Objects.requireNonNull(preferReturnType, "preferReturnType"); Objects.requireNonNull(in, "in"); Objects.requireNonNull(mediaType, "mediaType"); // securityContextReference may be null Builder request = getResource().path("Binary").request().header(Constants.HEADER_PREFER, - returnType.getHeaderValue()); + preferReturnType.getHeaderValue()); if (securityContextReference != null && !securityContextReference.isBlank()) request = request.header(Constants.HEADER_X_SECURITY_CONTEXT, securityContextReference); Response response = request.accept(Constants.CT_FHIR_JSON_NEW).post(Entity.entity(in, mediaType)); @@ -286,19 +401,20 @@ PreferReturn createBinary(PreferReturnType returnType, InputStream in, MediaType logStatusAndHeaders(response); if (Status.CREATED.getStatusCode() == response.getStatus()) - return toPreferReturn(returnType, Binary.class, response); + return toPreferReturn(preferReturnType, Binary.class, response); else throw handleError(response); } - PreferReturn update(PreferReturnType returnType, Resource resource) + PreferReturn update(PreferReturnType preferReturnType, Class returnType, R resource) { + Objects.requireNonNull(preferReturnType, "preferReturnType"); Objects.requireNonNull(returnType, "returnType"); Objects.requireNonNull(resource, "resource"); Builder builder = getResource().path(resource.getClass().getAnnotation(ResourceDef.class).name()) .path(resource.getIdElement().getIdPart()).request() - .header(Constants.HEADER_PREFER, returnType.getHeaderValue()).accept(Constants.CT_FHIR_JSON_NEW); + .header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()).accept(Constants.CT_FHIR_JSON_NEW); if (resource.getMeta().hasVersionId()) builder.header(Constants.HEADER_IF_MATCH, new EntityTag(resource.getMeta().getVersionId(), true)); @@ -308,13 +424,15 @@ PreferReturn update(PreferReturnType returnType, Resource resource) logStatusAndHeaders(response); if (Status.OK.getStatusCode() == response.getStatus()) - return toPreferReturn(returnType, resource.getClass(), response); + return toPreferReturn(preferReturnType, returnType, response); else throw handleError(response); } - PreferReturn updateConditionaly(PreferReturnType returnType, Resource resource, Map> criteria) + PreferReturn updateConditionaly(PreferReturnType preferReturnType, Class returnType, + R resource, Map> criteria) { + Objects.requireNonNull(preferReturnType, "preferReturnType"); Objects.requireNonNull(returnType, "returnType"); Objects.requireNonNull(resource, "resource"); Objects.requireNonNull(criteria, "criteria"); @@ -327,7 +445,7 @@ PreferReturn updateConditionaly(PreferReturnType returnType, Resource resource, target = target.queryParam(entry.getKey(), entry.getValue().toArray()); Builder builder = target.request().accept(Constants.CT_FHIR_JSON_NEW).header(Constants.HEADER_PREFER, - returnType.getHeaderValue()); + preferReturnType.getHeaderValue()); if (resource.getMeta().hasVersionId()) builder.header(Constants.HEADER_IF_MATCH, new EntityTag(resource.getMeta().getVersionId(), true)); @@ -337,22 +455,22 @@ PreferReturn updateConditionaly(PreferReturnType returnType, Resource resource, logStatusAndHeaders(response); if (Status.CREATED.getStatusCode() == response.getStatus() || Status.OK.getStatusCode() == response.getStatus()) - return toPreferReturn(returnType, resource.getClass(), response); + return toPreferReturn(preferReturnType, returnType, response); else throw handleError(response); } - PreferReturn updateBinary(PreferReturnType returnType, String id, InputStream in, MediaType mediaType, + PreferReturn updateBinary(PreferReturnType preferReturnType, String id, InputStream in, MediaType mediaType, String securityContextReference) { - Objects.requireNonNull(returnType, "returnType"); + Objects.requireNonNull(preferReturnType, "preferReturnType"); Objects.requireNonNull(id, "id"); Objects.requireNonNull(in, "in"); Objects.requireNonNull(mediaType, "mediaType"); // securityContextReference may be null Builder request = getResource().path("Binary").path(id).request().header(Constants.HEADER_PREFER, - returnType.getHeaderValue()); + preferReturnType.getHeaderValue()); if (securityContextReference != null && !securityContextReference.isBlank()) request = request.header(Constants.HEADER_X_SECURITY_CONTEXT, securityContextReference); Response response = request.accept(Constants.CT_FHIR_JSON_NEW).put(Entity.entity(in, mediaType)); @@ -360,7 +478,7 @@ PreferReturn updateBinary(PreferReturnType returnType, String id, InputStream in logStatusAndHeaders(response); if (Status.CREATED.getStatusCode() == response.getStatus()) - return toPreferReturn(returnType, Binary.class, response); + return toPreferReturn(preferReturnType, Binary.class, response); else throw handleError(response); } @@ -384,42 +502,44 @@ Bundle postBundle(PreferReturnType returnType, Bundle bundle) @SuppressWarnings("unchecked") public R create(R resource) { - return (R) create(PreferReturnType.REPRESENTATION, resource).getResource(); + return (R) create(PreferReturnType.REPRESENTATION, (Class) resource.getClass(), resource).resource(); } @Override @SuppressWarnings("unchecked") public R createConditionaly(R resource, String ifNoneExistCriteria) { - return (R) createConditionaly(PreferReturnType.REPRESENTATION, resource, ifNoneExistCriteria).getResource(); + return (R) createConditionaly(PreferReturnType.REPRESENTATION, (Class) resource.getClass(), resource, + ifNoneExistCriteria).resource(); } @Override public Binary createBinary(InputStream in, MediaType mediaType, String securityContextReference) { return (Binary) createBinary(PreferReturnType.REPRESENTATION, in, mediaType, securityContextReference) - .getResource(); + .resource(); } @Override @SuppressWarnings("unchecked") public R update(R resource) { - return (R) update(PreferReturnType.REPRESENTATION, resource).getResource(); + return (R) update(PreferReturnType.REPRESENTATION, (Class) resource.getClass(), resource).resource(); } @Override @SuppressWarnings("unchecked") public R updateConditionaly(R resource, Map> criteria) { - return (R) updateConditionaly(PreferReturnType.REPRESENTATION, resource, criteria).getResource(); + return (R) updateConditionaly(PreferReturnType.REPRESENTATION, (Class) resource.getClass(), resource, + criteria).resource(); } @Override public Binary updateBinary(String id, InputStream in, MediaType mediaType, String securityContextReference) { return (Binary) updateBinary(PreferReturnType.REPRESENTATION, id, in, mediaType, securityContextReference) - .getResource(); + .resource(); } @Override @@ -881,6 +1001,330 @@ public Bundle searchWithStrictHandling(Class resourceType, M throw handleError(response); } + @Override + public CompletableFuture searchAsync(DelayStrategy delayStrategy, Class resourceType, + Map> parameters) + { + Objects.requireNonNull(resourceType, "resourceType"); + + WebTarget target = getResource().path(resourceType.getAnnotation(ResourceDef.class).name()); + if (parameters != null) + { + for (Entry> entry : parameters.entrySet()) + target = target.queryParam(entry.getKey(), entry.getValue().toArray()); + } + + return searchAsync(delayStrategy, target, false); + } + + @Override + public CompletableFuture searchAsync(DelayStrategy delayStrategy, String url) + { + checkUri(url); + + return searchAsync(delayStrategy, client.target(url), false); + } + + @Override + public CompletableFuture searchAsyncWithStrictHandling(DelayStrategy delayStrategy, + Class resourceType, Map> parameters) + { + Objects.requireNonNull(resourceType, "resourceType"); + + WebTarget target = getResource().path(resourceType.getAnnotation(ResourceDef.class).name()); + if (parameters != null) + { + for (Entry> entry : parameters.entrySet()) + target = target.queryParam(entry.getKey(), entry.getValue().toArray()); + } + + return searchAsync(delayStrategy, target, true); + } + + @Override + public CompletableFuture searchAsyncWithStrictHandling(DelayStrategy delayStrategy, String url) + { + checkUri(url); + + return searchAsync(delayStrategy, client.target(url), true); + } + + private void checkUri(String url) + { + Objects.requireNonNull(url, "url"); + if (url.isBlank()) + throw new RuntimeException("url is blank"); + if (!url.startsWith(baseUrl)) + throw new RuntimeException("url not starting with client base url"); + if (url.startsWith(baseUrl + "@")) + throw new RuntimeException("url starting with client base url + @"); + } + + private CompletableFuture searchAsync(DelayStrategy delayStrategy, WebTarget target, boolean strict) + { + Builder requestBuilder = target.request().header(Constants.HEADER_PREFER, + Constants.HEADER_PREFER_RESPOND_ASYNC); + + if (strict) + requestBuilder.header(Constants.HEADER_PREFER, PreferHandlingType.STRICT.getHeaderValue()); + + return executeAsync(delayStrategy, requestBuilder, Bundle.class, PreferReturnType.REPRESENTATION, + AsyncInvoker::get).thenApply(PreferReturn::resource); + } + + private CompletableFuture> executeAsync(DelayStrategy delayStrategy, + Builder requestBuilder, Class returnType, PreferReturnType preferReturnType, + BiFunction, Future> httpMethod) + { + CompletableFuture> resultFuture = new CompletableFuture<>(); + InvocationCallback callback = new InvocationCallback() + { + @Override + public void completed(Response response) + { + if (Status.OK.getStatusCode() == response.getStatus()) + { + PreferReturn preferReturn = toPreferReturn(preferReturnType, returnType, response); + resultFuture.complete(preferReturn); + } + else if (Status.ACCEPTED.getStatusCode() == response.getStatus()) + { + response.close(); + + Optional retryAfter = parseRetryAfter(response.getHeaderString(HttpHeaders.RETRY_AFTER)); + + String contentLocation = response.getHeaderString(HttpHeaders.CONTENT_LOCATION); + if (contentLocation != null && !contentLocation.isBlank()) + { + checkUri(contentLocation); + pollUntilComplete(contentLocation, false, delayStrategy, retryAfter, resultFuture, returnType, + preferReturnType); + + return; + } + + String location = response.getHeaderString(HttpHeaders.LOCATION); + if (location != null && !location.isBlank()) + { + checkUri(location); + pollUntilComplete(location, true, delayStrategy, retryAfter, resultFuture, returnType, + preferReturnType); + + return; + } + + resultFuture.completeExceptionally( + new WebApplicationException("Reponse from server without " + HttpHeaders.CONTENT_LOCATION + + " or " + HttpHeaders.LOCATION + " header", Status.BAD_GATEWAY)); + } + else + resultFuture.completeExceptionally(handleError(response)); + } + + @Override + public void failed(Throwable throwable) + { + resultFuture.completeExceptionally(throwable); + } + }; + + AsyncInvoker async = requestBuilder.header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()) + .accept(Constants.CT_FHIR_JSON_NEW).async(); + + httpMethod.apply(async, callback); + + return resultFuture; + } + + private void pollUntilComplete(String location, boolean sendAcceptHeader, + DelayStrategy delayStrategy, Optional retryAfter, CompletableFuture> resultFuture, + Class resourceType, PreferReturnType preferReturnType) + { + Runnable poll = new Runnable() + { + private Duration delay = delayStrategy.getFirstDelay(); + + @Override + public void run() + { + if (resultFuture.isCancelled()) + return; + + try + { + Builder request = client.target(location).request(); + if (sendAcceptHeader) + request = request.accept(Constants.CT_FHIR_JSON_NEW); + + Response response = request.get(); + + if (Status.OK.getStatusCode() == response.getStatus()) + { + Bundle bundle = response.readEntity(Bundle.class); + PreferReturn r = DsfClientJersey.this.unpack(bundle, resourceType, preferReturnType); + + resultFuture.complete(r); + } + else if (Status.ACCEPTED.getStatusCode() == response.getStatus()) + { + response.close(); + + Optional retryAfter = parseRetryAfter( + response.getHeaderString(HttpHeaders.RETRY_AFTER)); + + delay = retryAfter.orElse(delayStrategy.getNextDelay(delay)); + logger.debug("Status 202, trying again in {}", delay); + scheduler.schedule(this, delay.toMillis(), TimeUnit.MILLISECONDS); + } + else + resultFuture.completeExceptionally(handleError(response)); + } + catch (Exception e) + { + resultFuture.completeExceptionally(e); + } + } + }; + + Duration delay = retryAfter.orElse(delayStrategy.getFirstDelay()); + logger.debug("Status 202, trying again in {}", delay); + scheduler.schedule(poll, delay.toMillis(), TimeUnit.MILLISECONDS); + } + + private Optional parseRetryAfter(String headerValue) + { + if (headerValue == null || headerValue.isBlank()) + return Optional.empty(); + + String trimmed = headerValue.trim(); + if (trimmed.chars().allMatch(Character::isDigit)) + { + try + { + long seconds = Long.parseLong(trimmed); + return Optional.of(Duration.ofSeconds(seconds)); + } + catch (NumberFormatException e) + { + logger.warn("Unable to parse header value: {}", e.getMessage()); + return Optional.empty(); + } + } + + try + { + ZonedDateTime retryTime = ZonedDateTime.parse(trimmed, DateTimeFormatter.ofPattern(RFC_7231_FORMAT)); + Duration duration = Duration.between(ZonedDateTime.now(), retryTime); + return Optional.of(duration.isNegative() ? Duration.ZERO : duration); + } + catch (DateTimeParseException e) + { + logger.warn("Unable to parse header value: {}", e.getMessage()); + return Optional.empty(); + } + } + + private PreferReturn unpack(Bundle bundle, Class resourceType, + PreferReturnType preferReturnType) + { + if (BundleType.BATCHRESPONSE.equals(bundle.getType())) + { + List entries = bundle.getEntry(); + if (entries.size() == 1) + { + BundleEntryComponent entry = entries.get(0); + if (entry.hasResponse()) + { + BundleEntryResponseComponent response = entry.getResponse(); + if (response.hasStatus()) + { + String status = response.getStatus(); + if ("200 OK".equals(status) || "200".equals(status)) + { + if (PreferReturnType.MINIMAL.equals(preferReturnType)) + return PreferReturn.minimal(response.getLocation()); + else if (PreferReturnType.OPERATION_OUTCOME.equals(preferReturnType)) + { + if (response.hasOutcome()) + { + Resource outcome = response.getOutcome(); + + if (outcome instanceof OperationOutcome o) + return PreferReturn.outcome(o); + else + throw new ProcessingException( + "Reponse from server not a Bundle with Bundle.entry[0].response.outcome of type OperationOutcome but " + + outcome.getResourceType().name()); + } + else + throw new ProcessingException( + "Reponse from server not a Bundle with Bundle.entry[0].response.outcome"); + } + else if (PreferReturnType.REPRESENTATION.equals(preferReturnType)) + { + if (entry.hasResource()) + { + Resource resource = entry.getResource(); + + if (resourceType.isInstance(resource)) + return PreferReturn.resource(resourceType.cast(resource)); + else + { + String resourceTypeName = resourceType.getAnnotation(ResourceDef.class).name(); + + throw new ProcessingException( + "Reponse from server not a Bundle with Bundle.entry[0].resource of type " + + resourceTypeName + " but " + + resource.getResourceType().name()); + } + } + else + throw new ProcessingException( + "Reponse from server not a Bundle with Bundle.entry[0].resource"); + } + else + throw new IllegalArgumentException(preferReturnType + " not supported"); + } + else + { + Matcher statusMatcher = HTTP_STATUS_PATTERN.matcher(status); + if (statusMatcher.matches()) + { + try + { + int code = Integer.parseInt(statusMatcher.group()); + throw new WebApplicationException("Bundle.entry[0].response.status: " + status, + Status.fromStatusCode(code)); + } + catch (NumberFormatException e) + { + throw new ProcessingException( + "Reponse from server not a Bundle with unkown Bundle.entry[0].response.status: " + + status, + e); + } + } + else + throw new ProcessingException( + "Reponse from server not a Bundle with unkown Bundle.entry[0].response.status: " + + status); + } + } + else + throw new ProcessingException( + "Reponse from server not a Bundle with Bundle.entry[0].response.status"); + } + else + throw new ProcessingException("Reponse from server not a Bundle with Bundle.entry[0].response"); + } + else + throw new ProcessingException("Reponse from server not a Bundle with one entry but " + entries.size()); + } + else + throw new ProcessingException("Reponse from server not a Bundle with type " + BundleType.BATCHRESPONSE + + " but " + bundle.getType()); + } + @Override public CapabilityStatement getConformance() { @@ -937,23 +1381,21 @@ public StructureDefinition generateSnapshot(StructureDefinition differential) } @Override - public BasicDsfClient withRetry(int nTimes, Duration delay) + public BasicDsfClient withRetry(int nTimes, DelayStrategy delayStrategy) { if (nTimes < 0) throw new IllegalArgumentException("nTimes < 0"); - if (delay == null || delay.isNegative()) - throw new IllegalArgumentException("delay null or negative"); + Objects.requireNonNull(delayStrategy, "delayStrategy"); - return new BasicDsfClientWithRetryImpl(this, nTimes, delay); + return new BasicDsfClientWithRetryImpl(this, nTimes, delayStrategy); } @Override - public BasicDsfClient withRetryForever(Duration delay) + public BasicDsfClient withRetryForever(DelayStrategy delayStrategy) { - if (delay == null || delay.isNegative()) - throw new IllegalArgumentException("delay null or negative"); + Objects.requireNonNull(delayStrategy, "delayStrategy"); - return new BasicDsfClientWithRetryImpl(this, RETRY_FOREVER, delay); + return new BasicDsfClientWithRetryImpl(this, RETRY_FOREVER, delayStrategy); } @Override @@ -982,4 +1424,247 @@ public Bundle history(Class resourceType, String id, int pag else throw handleError(response); } + + PreferReturn operation(PreferReturnType preferReturnType, String operationName, + Parameters parameters, Class returnType) + { + Objects.requireNonNull(preferReturnType, "preferReturnType"); + Objects.requireNonNull(operationName, "operationName"); + // parameters may be null + Objects.requireNonNull(returnType, "returnType"); + + operationName = !operationName.startsWith("$") ? "$" + operationName : operationName; + + Response response = getResource().path(operationName).request() + .header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()).accept(Constants.CT_FHIR_JSON_NEW) + .post(Entity.entity(parameters, Constants.CT_FHIR_JSON_NEW)); + + logger.debug("HTTP {}: {}", response.getStatusInfo().getStatusCode(), + response.getStatusInfo().getReasonPhrase()); + if (Status.OK.getStatusCode() == response.getStatus()) + return toPreferReturn(preferReturnType, returnType, response); + else + throw handleError(response); + } + + PreferReturn operation(PreferReturnType preferReturnType, + Class resourceType, String operationName, Parameters parameters, Class returnType) + { + Objects.requireNonNull(preferReturnType, "preferReturnType"); + Objects.requireNonNull(resourceType, "resourceType"); + Objects.requireNonNull(operationName, "operationName"); + // parameters may be null + Objects.requireNonNull(returnType, "returnType"); + + operationName = !operationName.startsWith("$") ? "$" + operationName : operationName; + + Response response = getResource().path(resourceType.getAnnotation(ResourceDef.class).name()).path(operationName) + .request().header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()) + .accept(Constants.CT_FHIR_JSON_NEW).post(Entity.entity(parameters, Constants.CT_FHIR_JSON_NEW)); + + logger.debug("HTTP {}: {}", response.getStatusInfo().getStatusCode(), + response.getStatusInfo().getReasonPhrase()); + if (Status.OK.getStatusCode() == response.getStatus()) + return toPreferReturn(preferReturnType, returnType, response); + else + throw handleError(response); + } + + PreferReturn operation(PreferReturnType preferReturnType, + Class resourceType, String id, String operationName, Parameters parameters, Class returnType) + { + Objects.requireNonNull(preferReturnType, "preferReturnType"); + Objects.requireNonNull(resourceType, "resourceType"); + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(operationName, "operationName"); + // parameters may be null + Objects.requireNonNull(returnType, "returnType"); + + operationName = !operationName.startsWith("$") ? "$" + operationName : operationName; + + Response response = getResource().path(resourceType.getAnnotation(ResourceDef.class).name()).path(id) + .path(operationName).request().header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()) + .accept(Constants.CT_FHIR_JSON_NEW).post(Entity.entity(parameters, Constants.CT_FHIR_JSON_NEW)); + + logger.debug("HTTP {}: {}", response.getStatusInfo().getStatusCode(), + response.getStatusInfo().getReasonPhrase()); + if (Status.OK.getStatusCode() == response.getStatus()) + return toPreferReturn(preferReturnType, returnType, response); + else + throw handleError(response); + } + + PreferReturn operation(PreferReturnType preferReturnType, + Class resourceType, String id, String version, String operationName, Parameters parameters, + Class returnType) + { + Objects.requireNonNull(preferReturnType, "preferReturnType"); + Objects.requireNonNull(resourceType, "resourceType"); + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(version, "version"); + Objects.requireNonNull(operationName, "operationName"); + // parameters may be null + Objects.requireNonNull(returnType, "returnType"); + + operationName = !operationName.startsWith("$") ? "$" + operationName : operationName; + + Response response = getResource().path(resourceType.getAnnotation(ResourceDef.class).name()).path(id) + .path("_history").path(version).path(operationName).request() + .header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()).accept(Constants.CT_FHIR_JSON_NEW) + .post(Entity.entity(parameters, Constants.CT_FHIR_JSON_NEW)); + + logger.debug("HTTP {}: {}", response.getStatusInfo().getStatusCode(), + response.getStatusInfo().getReasonPhrase()); + if (Status.OK.getStatusCode() == response.getStatus()) + return toPreferReturn(preferReturnType, returnType, response); + else + throw handleError(response); + } + + @Override + public R operation(String operationName, Parameters parameters, Class returnType) + { + return operation(PreferReturnType.REPRESENTATION, operationName, parameters, returnType).resource(); + } + + @Override + public R operation(Class resourceType, String operationName, + Parameters parameters, Class returnType) + { + return operation(PreferReturnType.REPRESENTATION, resourceType, operationName, parameters, returnType) + .resource(); + } + + @Override + public R operation(Class resourceType, String id, String operationName, + Parameters parameters, Class returnType) + { + return operation(PreferReturnType.REPRESENTATION, resourceType, id, operationName, parameters, returnType) + .resource(); + } + + @Override + public R operation(Class resourceType, String id, String version, + String operationName, Parameters parameters, Class returnType) + { + return operation(PreferReturnType.REPRESENTATION, resourceType, id, version, operationName, parameters, + returnType).resource(); + } + + CompletableFuture> operationAsync(PreferReturnType preferReturnType, + DelayStrategy delayStrategy, String operationName, Parameters parameters, Class returnType) + { + Objects.requireNonNull(preferReturnType, "preferReturnType"); + Objects.requireNonNull(delayStrategy, "delayStrategy"); + Objects.requireNonNull(operationName, "operationName"); + // parameters may be null + Objects.requireNonNull(returnType, "returnType"); + + operationName = !operationName.startsWith("$") ? "$" + operationName : operationName; + + Builder requestBuilder = getResource().path(operationName).request() + .header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()).accept(Constants.CT_FHIR_JSON_NEW); + + return executeAsync(delayStrategy, requestBuilder, returnType, preferReturnType, + (async, callback) -> async.post(Entity.entity(parameters, Constants.CT_FHIR_JSON_NEW), callback)); + } + + CompletableFuture> operationAsync( + PreferReturnType preferReturnType, DelayStrategy delayStrategy, Class resourceType, String operationName, + Parameters parameters, Class returnType) + { + Objects.requireNonNull(preferReturnType, "preferReturnType"); + Objects.requireNonNull(delayStrategy, "delayStrategy"); + Objects.requireNonNull(resourceType, "resourceType"); + Objects.requireNonNull(operationName, "operationName"); + // parameters may be null + Objects.requireNonNull(returnType, "returnType"); + + operationName = !operationName.startsWith("$") ? "$" + operationName : operationName; + + Builder requestBuilder = getResource().path(resourceType.getAnnotation(ResourceDef.class).name()) + .path(operationName).request().header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()) + .accept(Constants.CT_FHIR_JSON_NEW); + + return executeAsync(delayStrategy, requestBuilder, returnType, preferReturnType, + (async, callback) -> async.post(Entity.entity(parameters, Constants.CT_FHIR_JSON_NEW), callback)); + } + + CompletableFuture> operationAsync( + PreferReturnType preferReturnType, DelayStrategy delayStrategy, Class resourceType, String id, + String operationName, Parameters parameters, Class returnType) + { + Objects.requireNonNull(preferReturnType, "preferReturnType"); + Objects.requireNonNull(delayStrategy, "delayStrategy"); + Objects.requireNonNull(resourceType, "resourceType"); + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(operationName, "operationName"); + // parameters may be null + Objects.requireNonNull(returnType, "returnType"); + + operationName = !operationName.startsWith("$") ? "$" + operationName : operationName; + + Builder requestBuilder = getResource().path(resourceType.getAnnotation(ResourceDef.class).name()).path(id) + .path(operationName).request().header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()) + .accept(Constants.CT_FHIR_JSON_NEW); + + return executeAsync(delayStrategy, requestBuilder, returnType, preferReturnType, + (async, callback) -> async.post(Entity.entity(parameters, Constants.CT_FHIR_JSON_NEW), callback)); + } + + CompletableFuture> operationAsync( + PreferReturnType preferReturnType, DelayStrategy delayStrategy, Class resourceType, String id, + String version, String operationName, Parameters parameters, Class returnType) + { + Objects.requireNonNull(preferReturnType, "preferReturnType"); + Objects.requireNonNull(delayStrategy, "delayStrategy"); + Objects.requireNonNull(resourceType, "resourceType"); + Objects.requireNonNull(id, "id"); + Objects.requireNonNull(version, "version"); + Objects.requireNonNull(operationName, "operationName"); + // parameters may be null + Objects.requireNonNull(returnType, "returnType"); + + operationName = !operationName.startsWith("$") ? "$" + operationName : operationName; + + Builder requestBuilder = getResource().path(resourceType.getAnnotation(ResourceDef.class).name()).path(id) + .path("_history").path(version).path(operationName).request() + .header(Constants.HEADER_PREFER, preferReturnType.getHeaderValue()).accept(Constants.CT_FHIR_JSON_NEW); + + return executeAsync(delayStrategy, requestBuilder, returnType, preferReturnType, + (async, callback) -> async.post(Entity.entity(parameters, Constants.CT_FHIR_JSON_NEW), callback)); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, String operationName, + Parameters parameters, Class returnType) + { + return operationAsync(PreferReturnType.REPRESENTATION, delayStrategy, operationName, parameters, returnType) + .thenApply(PreferReturn::resource); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String operationName, Parameters parameters, Class returnType) + { + return operationAsync(PreferReturnType.REPRESENTATION, delayStrategy, resourceType, operationName, parameters, + returnType).thenApply(PreferReturn::resource); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String operationName, Parameters parameters, Class returnType) + { + return operationAsync(PreferReturnType.REPRESENTATION, delayStrategy, resourceType, id, operationName, + parameters, returnType).thenApply(PreferReturn::resource); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String version, String operationName, Parameters parameters, + Class returnType) + { + return operationAsync(PreferReturnType.REPRESENTATION, delayStrategy, resourceType, id, version, operationName, + parameters, returnType).thenApply(PreferReturn::resource); + } } diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturn.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturn.java index bc18da106..a76481580 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturn.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturn.java @@ -15,52 +15,24 @@ */ package dev.dsf.bpe.v2.client.dsf; -import java.net.URI; - import org.hl7.fhir.r4.model.IdType; import org.hl7.fhir.r4.model.OperationOutcome; import org.hl7.fhir.r4.model.Resource; -public class PreferReturn +public record PreferReturn(IdType id, R resource, OperationOutcome operationOutcome) { - private final IdType id; - private final Resource resource; - private final OperationOutcome operationOutcome; - - private PreferReturn(IdType id, Resource resource, OperationOutcome operationOutcome) - { - this.id = id; - this.resource = resource; - this.operationOutcome = operationOutcome; - } - - public static PreferReturn minimal(URI location) - { - return new PreferReturn(new IdType(location.toString()), null, null); - } - - public static PreferReturn resource(Resource resource) - { - return new PreferReturn(null, resource, null); - } - - public static PreferReturn outcome(OperationOutcome operationOutcome) - { - return new PreferReturn(null, null, operationOutcome); - } - - public IdType getId() + public static PreferReturn minimal(String location) { - return id; + return new PreferReturn<>(new IdType(location), null, null); } - public Resource getResource() + public static PreferReturn resource(R resource) { - return resource; + return new PreferReturn<>(null, resource, null); } - public OperationOutcome getOperationOutcome() + public static PreferReturn outcome(OperationOutcome operationOutcome) { - return operationOutcome; + return new PreferReturn<>(null, null, operationOutcome); } } diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalRetryImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalRetryImpl.java index 3a6fbe665..9081efe1a 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalRetryImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalRetryImpl.java @@ -16,60 +16,61 @@ package dev.dsf.bpe.v2.client.dsf; import java.io.InputStream; -import java.time.Duration; import java.util.List; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import jakarta.ws.rs.core.MediaType; class PreferReturnMinimalRetryImpl extends AbstractDsfClientJerseyWithRetry implements PreferReturnMinimal { - PreferReturnMinimalRetryImpl(DsfClientJersey delegate, int nTimes, Duration delay) + PreferReturnMinimalRetryImpl(DsfClientJersey delegate, int nTimes, DelayStrategy delayStrategy) { - super(delegate, nTimes, delay); + super(delegate, nTimes, delayStrategy); } @Override public IdType create(Resource resource) { - return retry(() -> delegate.create(PreferReturnType.MINIMAL, resource).getId()); + return retry(() -> delegate.create(PreferReturnType.MINIMAL, Resource.class, resource).id()); } @Override public IdType createConditionaly(Resource resource, String ifNoneExistCriteria) { - return retry( - () -> delegate.createConditionaly(PreferReturnType.MINIMAL, resource, ifNoneExistCriteria).getId()); + return retry(() -> delegate + .createConditionaly(PreferReturnType.MINIMAL, Resource.class, resource, ifNoneExistCriteria).id()); } @Override public IdType createBinary(InputStream in, MediaType mediaType, String securityContextReference) { return retry( - () -> delegate.createBinary(PreferReturnType.MINIMAL, in, mediaType, securityContextReference).getId()); + () -> delegate.createBinary(PreferReturnType.MINIMAL, in, mediaType, securityContextReference).id()); } @Override public IdType update(Resource resource) { - return retry(() -> delegate.update(PreferReturnType.MINIMAL, resource).getId()); + return retry(() -> delegate.update(PreferReturnType.MINIMAL, Resource.class, resource).id()); } @Override public IdType updateConditionaly(Resource resource, Map> criteria) { - return retry(() -> delegate.updateConditionaly(PreferReturnType.MINIMAL, resource, criteria).getId()); + return retry( + () -> delegate.updateConditionaly(PreferReturnType.MINIMAL, Resource.class, resource, criteria).id()); } @Override public IdType updateBinary(String id, InputStream in, MediaType mediaType, String securityContextReference) { return retry(() -> delegate.updateBinary(PreferReturnType.MINIMAL, id, in, mediaType, securityContextReference) - .getId()); + .id()); } @Override @@ -77,4 +78,34 @@ public Bundle postBundle(Bundle bundle) { return retry(() -> delegate.postBundle(PreferReturnType.MINIMAL, bundle)); } + + @Override + public IdType operation(String operationName, Parameters parameters) + { + return retry( + () -> delegate.operation(PreferReturnType.MINIMAL, operationName, parameters, Resource.class).id()); + } + + @Override + public IdType operation(Class resourceType, String operationName, Parameters parameters) + { + return retry(() -> delegate + .operation(PreferReturnType.MINIMAL, resourceType, operationName, parameters, Resource.class).id()); + } + + @Override + public IdType operation(Class resourceType, String id, String operationName, + Parameters parameters) + { + return retry(() -> delegate + .operation(PreferReturnType.MINIMAL, resourceType, id, operationName, parameters, Resource.class).id()); + } + + @Override + public IdType operation(Class resourceType, String id, String version, String operationName, + Parameters parameters) + { + return retry(() -> delegate.operation(PreferReturnType.MINIMAL, resourceType, id, version, operationName, + parameters, Resource.class).id()); + } } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalWithRetryImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalWithRetryImpl.java index 3e3ab9bb9..b83becd3f 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalWithRetryImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalWithRetryImpl.java @@ -16,17 +16,18 @@ package dev.dsf.bpe.v2.client.dsf; import java.io.InputStream; -import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import jakarta.ws.rs.core.MediaType; -class PreferReturnMinimalWithRetryImpl implements PreferReturnMinimalWithRetry +class PreferReturnMinimalWithRetryImpl implements PreferReturnMinimalWithRetry, AsyncPreferReturnMinimal { private final DsfClientJersey delegate; @@ -38,37 +39,38 @@ class PreferReturnMinimalWithRetryImpl implements PreferReturnMinimalWithRetry @Override public IdType create(Resource resource) { - return delegate.create(PreferReturnType.MINIMAL, resource).getId(); + return delegate.create(PreferReturnType.MINIMAL, Resource.class, resource).id(); } @Override public IdType createConditionaly(Resource resource, String ifNoneExistCriteria) { - return delegate.createConditionaly(PreferReturnType.MINIMAL, resource, ifNoneExistCriteria).getId(); + return delegate.createConditionaly(PreferReturnType.MINIMAL, Resource.class, resource, ifNoneExistCriteria) + .id(); } @Override public IdType createBinary(InputStream in, MediaType mediaType, String securityContextReference) { - return delegate.createBinary(PreferReturnType.MINIMAL, in, mediaType, securityContextReference).getId(); + return delegate.createBinary(PreferReturnType.MINIMAL, in, mediaType, securityContextReference).id(); } @Override public IdType update(Resource resource) { - return delegate.update(PreferReturnType.MINIMAL, resource).getId(); + return delegate.update(PreferReturnType.MINIMAL, Resource.class, resource).id(); } @Override public IdType updateConditionaly(Resource resource, Map> criteria) { - return delegate.updateConditionaly(PreferReturnType.MINIMAL, resource, criteria).getId(); + return delegate.updateConditionaly(PreferReturnType.MINIMAL, Resource.class, resource, criteria).id(); } @Override public IdType updateBinary(String id, InputStream in, MediaType mediaType, String securityContextReference) { - return delegate.updateBinary(PreferReturnType.MINIMAL, id, in, mediaType, securityContextReference).getId(); + return delegate.updateBinary(PreferReturnType.MINIMAL, id, in, mediaType, securityContextReference).id(); } @Override @@ -78,22 +80,84 @@ public Bundle postBundle(Bundle bundle) } @Override - public PreferReturnMinimal withRetry(int nTimes, Duration delay) + public PreferReturnMinimal withRetry(int nTimes, DelayStrategy delayStrategy) { if (nTimes < 0) throw new IllegalArgumentException("nTimes < 0"); - if (delay == null || delay.isNegative()) - throw new IllegalArgumentException("delay null or negative"); + if (delayStrategy == null) + throw new IllegalArgumentException("delayStrategy null"); - return new PreferReturnMinimalRetryImpl(delegate, nTimes, delay); + return new PreferReturnMinimalRetryImpl(delegate, nTimes, delayStrategy); } @Override - public PreferReturnMinimal withRetryForever(Duration delay) + public PreferReturnMinimal withRetryForever(DelayStrategy delayStrategy) { - if (delay == null || delay.isNegative()) - throw new IllegalArgumentException("delay null or negative"); + if (delayStrategy == null) + throw new IllegalArgumentException("delayStrategy null"); - return new PreferReturnMinimalRetryImpl(delegate, RETRY_FOREVER, delay); + return new PreferReturnMinimalRetryImpl(delegate, RETRY_FOREVER, delayStrategy); + } + + @Override + public IdType operation(String operationName, Parameters parameters) + { + return delegate.operation(PreferReturnType.MINIMAL, operationName, parameters, Resource.class).id(); + } + + @Override + public IdType operation(Class resourceType, String operationName, Parameters parameters) + { + return delegate.operation(PreferReturnType.MINIMAL, resourceType, operationName, parameters, Resource.class) + .id(); + } + + @Override + public IdType operation(Class resourceType, String id, String operationName, + Parameters parameters) + { + return delegate.operation(PreferReturnType.MINIMAL, resourceType, id, operationName, parameters, Resource.class) + .id(); + } + + @Override + public IdType operation(Class resourceType, String id, String version, String operationName, + Parameters parameters) + { + return delegate.operation(PreferReturnType.MINIMAL, resourceType, id, version, operationName, parameters, + Resource.class).id(); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, String operationName, + Parameters parameters) + { + return delegate + .operationAsync(PreferReturnType.MINIMAL, delayStrategy, operationName, parameters, Resource.class) + .thenApply(PreferReturn::id); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String operationName, Parameters parameters) + { + return delegate.operationAsync(PreferReturnType.MINIMAL, delayStrategy, resourceType, operationName, parameters, + Resource.class).thenApply(PreferReturn::id); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String operationName, Parameters parameters) + { + return delegate.operationAsync(PreferReturnType.MINIMAL, delayStrategy, resourceType, id, operationName, + parameters, Resource.class).thenApply(PreferReturn::id); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String version, String operationName, Parameters parameters) + { + return delegate.operationAsync(PreferReturnType.MINIMAL, delayStrategy, resourceType, id, version, + operationName, parameters, Resource.class).thenApply(PreferReturn::id); } } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeRetryImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeRetryImpl.java index b38ac37a6..a88da72a6 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeRetryImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeRetryImpl.java @@ -16,35 +16,36 @@ package dev.dsf.bpe.v2.client.dsf; import java.io.InputStream; -import java.time.Duration; import java.util.List; import java.util.Map; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import jakarta.ws.rs.core.MediaType; class PreferReturnOutcomeRetryImpl extends AbstractDsfClientJerseyWithRetry implements PreferReturnOutcome { - PreferReturnOutcomeRetryImpl(DsfClientJersey delegate, int nTimes, Duration delay) + PreferReturnOutcomeRetryImpl(DsfClientJersey delegate, int nTimes, DelayStrategy delayStrategy) { - super(delegate, nTimes, delay); + super(delegate, nTimes, delayStrategy); } @Override public OperationOutcome create(Resource resource) { - return retry(() -> delegate.create(PreferReturnType.OPERATION_OUTCOME, resource).getOperationOutcome()); + return retry( + () -> delegate.create(PreferReturnType.OPERATION_OUTCOME, Resource.class, resource).operationOutcome()); } @Override public OperationOutcome createConditionaly(Resource resource, String ifNoneExistCriteria) { - return retry( - () -> delegate.createConditionaly(PreferReturnType.OPERATION_OUTCOME, resource, ifNoneExistCriteria) - .getOperationOutcome()); + return retry(() -> delegate + .createConditionaly(PreferReturnType.OPERATION_OUTCOME, Resource.class, resource, ifNoneExistCriteria) + .operationOutcome()); } @Override @@ -52,20 +53,22 @@ public OperationOutcome createBinary(InputStream in, MediaType mediaType, String { return retry( () -> delegate.createBinary(PreferReturnType.OPERATION_OUTCOME, in, mediaType, securityContextReference) - .getOperationOutcome()); + .operationOutcome()); } @Override public OperationOutcome update(Resource resource) { - return retry(() -> delegate.update(PreferReturnType.OPERATION_OUTCOME, resource).getOperationOutcome()); + return retry( + () -> delegate.update(PreferReturnType.OPERATION_OUTCOME, Resource.class, resource).operationOutcome()); } @Override public OperationOutcome updateConditionaly(Resource resource, Map> criteria) { - return retry(() -> delegate.updateConditionaly(PreferReturnType.OPERATION_OUTCOME, resource, criteria) - .getOperationOutcome()); + return retry(() -> delegate + .updateConditionaly(PreferReturnType.OPERATION_OUTCOME, Resource.class, resource, criteria) + .operationOutcome()); } @Override @@ -74,7 +77,7 @@ public OperationOutcome updateBinary(String id, InputStream in, MediaType mediaT { return retry(() -> delegate .updateBinary(PreferReturnType.OPERATION_OUTCOME, id, in, mediaType, securityContextReference) - .getOperationOutcome()); + .operationOutcome()); } @Override @@ -82,4 +85,37 @@ public Bundle postBundle(Bundle bundle) { return retry(() -> delegate.postBundle(PreferReturnType.OPERATION_OUTCOME, bundle)); } + + @Override + public OperationOutcome operation(String operationName, Parameters parameters) + { + return retry( + () -> delegate.operation(PreferReturnType.OPERATION_OUTCOME, operationName, parameters, Resource.class) + .operationOutcome()); + } + + @Override + public OperationOutcome operation(Class resourceType, String operationName, + Parameters parameters) + { + return retry(() -> delegate + .operation(PreferReturnType.OPERATION_OUTCOME, resourceType, operationName, parameters, Resource.class) + .operationOutcome()); + } + + @Override + public OperationOutcome operation(Class resourceType, String id, String operationName, + Parameters parameters) + { + return retry(() -> delegate.operation(PreferReturnType.OPERATION_OUTCOME, resourceType, id, operationName, + parameters, Resource.class).operationOutcome()); + } + + @Override + public OperationOutcome operation(Class resourceType, String id, String version, + String operationName, Parameters parameters) + { + return retry(() -> delegate.operation(PreferReturnType.OPERATION_OUTCOME, resourceType, id, version, + operationName, parameters, Resource.class).operationOutcome()); + } } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeWithRetryImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeWithRetryImpl.java index adc5bd015..3e5734a43 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeWithRetryImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeWithRetryImpl.java @@ -16,17 +16,18 @@ package dev.dsf.bpe.v2.client.dsf; import java.io.InputStream; -import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.concurrent.CompletableFuture; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import jakarta.ws.rs.core.MediaType; -class PreferReturnOutcomeWithRetryImpl implements PreferReturnOutcomeWithRetry +class PreferReturnOutcomeWithRetryImpl implements PreferReturnOutcomeWithRetry, AsyncPreferReturnOutcome { private final DsfClientJersey delegate; @@ -38,34 +39,35 @@ class PreferReturnOutcomeWithRetryImpl implements PreferReturnOutcomeWithRetry @Override public OperationOutcome create(Resource resource) { - return delegate.create(PreferReturnType.OPERATION_OUTCOME, resource).getOperationOutcome(); + return delegate.create(PreferReturnType.OPERATION_OUTCOME, Resource.class, resource).operationOutcome(); } @Override public OperationOutcome createConditionaly(Resource resource, String ifNoneExistCriteria) { - return delegate.createConditionaly(PreferReturnType.OPERATION_OUTCOME, resource, ifNoneExistCriteria) - .getOperationOutcome(); + return delegate + .createConditionaly(PreferReturnType.OPERATION_OUTCOME, Resource.class, resource, ifNoneExistCriteria) + .operationOutcome(); } @Override public OperationOutcome createBinary(InputStream in, MediaType mediaType, String securityContextReference) { return delegate.createBinary(PreferReturnType.OPERATION_OUTCOME, in, mediaType, securityContextReference) - .getOperationOutcome(); + .operationOutcome(); } @Override public OperationOutcome update(Resource resource) { - return delegate.update(PreferReturnType.OPERATION_OUTCOME, resource).getOperationOutcome(); + return delegate.update(PreferReturnType.OPERATION_OUTCOME, Resource.class, resource).operationOutcome(); } @Override public OperationOutcome updateConditionaly(Resource resource, Map> criteria) { - return delegate.updateConditionaly(PreferReturnType.OPERATION_OUTCOME, resource, criteria) - .getOperationOutcome(); + return delegate.updateConditionaly(PreferReturnType.OPERATION_OUTCOME, Resource.class, resource, criteria) + .operationOutcome(); } @Override @@ -73,7 +75,7 @@ public OperationOutcome updateBinary(String id, InputStream in, MediaType mediaT String securityContextReference) { return delegate.updateBinary(PreferReturnType.OPERATION_OUTCOME, id, in, mediaType, securityContextReference) - .getOperationOutcome(); + .operationOutcome(); } @Override @@ -83,22 +85,86 @@ public Bundle postBundle(Bundle bundle) } @Override - public PreferReturnOutcome withRetry(int nTimes, Duration delay) + public PreferReturnOutcome withRetry(int nTimes, DelayStrategy delayStrategy) { if (nTimes < 0) throw new IllegalArgumentException("nTimes < 0"); - if (delay == null || delay.isNegative()) - throw new IllegalArgumentException("delay null or negative"); + if (delayStrategy == null) + throw new IllegalArgumentException("delayStrategy null"); - return new PreferReturnOutcomeRetryImpl(delegate, nTimes, delay); + return new PreferReturnOutcomeRetryImpl(delegate, nTimes, delayStrategy); } @Override - public PreferReturnOutcome withRetryForever(Duration delay) + public PreferReturnOutcome withRetryForever(DelayStrategy delayStrategy) { - if (delay == null || delay.isNegative()) - throw new IllegalArgumentException("delay null or negative"); + if (delayStrategy == null) + throw new IllegalArgumentException("delayStrategy null"); - return new PreferReturnOutcomeRetryImpl(delegate, RETRY_FOREVER, delay); + return new PreferReturnOutcomeRetryImpl(delegate, RETRY_FOREVER, delayStrategy); + } + + @Override + public OperationOutcome operation(String operationName, Parameters parameters) + { + return delegate.operation(PreferReturnType.OPERATION_OUTCOME, operationName, parameters, Resource.class) + .operationOutcome(); + } + + @Override + public OperationOutcome operation(Class resourceType, String operationName, + Parameters parameters) + { + return delegate + .operation(PreferReturnType.OPERATION_OUTCOME, resourceType, operationName, parameters, Resource.class) + .operationOutcome(); + } + + @Override + public OperationOutcome operation(Class resourceType, String id, String operationName, + Parameters parameters) + { + return delegate.operation(PreferReturnType.OPERATION_OUTCOME, resourceType, id, operationName, parameters, + Resource.class).operationOutcome(); + } + + @Override + public OperationOutcome operation(Class resourceType, String id, String version, + String operationName, Parameters parameters) + { + return delegate.operation(PreferReturnType.OPERATION_OUTCOME, resourceType, id, version, operationName, + parameters, Resource.class).operationOutcome(); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, String operationName, + Parameters parameters) + { + return delegate.operationAsync(PreferReturnType.OPERATION_OUTCOME, delayStrategy, operationName, parameters, + Resource.class).thenApply(PreferReturn::operationOutcome); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String operationName, Parameters parameters) + { + return delegate.operationAsync(PreferReturnType.OPERATION_OUTCOME, delayStrategy, resourceType, operationName, + parameters, Resource.class).thenApply(PreferReturn::operationOutcome); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String operationName, Parameters parameters) + { + return delegate.operationAsync(PreferReturnType.OPERATION_OUTCOME, delayStrategy, resourceType, id, + operationName, parameters, Resource.class).thenApply(PreferReturn::operationOutcome); + } + + @Override + public CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String version, String operationName, Parameters parameters) + { + return delegate.operationAsync(PreferReturnType.OPERATION_OUTCOME, delayStrategy, resourceType, id, version, + operationName, parameters, Resource.class).thenApply(PreferReturn::operationOutcome); } } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/oidc/OidcInterceptor.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/oidc/OidcInterceptor.java index 400f7629b..06ea0be12 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/oidc/OidcInterceptor.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/client/oidc/OidcInterceptor.java @@ -17,6 +17,7 @@ import java.io.IOException; +import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.client.api.IClientInterceptor; import ca.uhn.fhir.rest.client.api.IHttpRequest; import ca.uhn.fhir.rest.client.api.IHttpResponse; @@ -35,7 +36,8 @@ public OidcInterceptor(OidcClient oidcClient) public void interceptRequest(IHttpRequest request) { char[] accessToken = oidcClient.getAccessToken(); - request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + String.valueOf(accessToken)); + request.addHeader(HttpHeaders.AUTHORIZATION, + Constants.HEADER_AUTHORIZATION_VALPREFIX_BEARER + String.valueOf(accessToken)); } @Override diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/plugin/ProcessPluginImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/plugin/ProcessPluginImpl.java index 32eaad585..364e22367 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/plugin/ProcessPluginImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/plugin/ProcessPluginImpl.java @@ -134,7 +134,7 @@ public ProcessPluginImpl(ProcessPluginDefinition processPluginDefinition, int pr this.processPluginDefinition = processPluginDefinition; variablesFactory = delegateExecution -> new VariablesImpl(delegateExecution, getObjectMapper(), - getProcessPluginApi().getDsfClientProvider().getLocalDsfClient()); + getProcessPluginApi().getDsfClientProvider().getLocal()); pluginMdc = new PluginMdcImpl(processPluginApiVersion, processPluginDefinition.getName(), processPluginDefinition.getVersion(), jarFile.toString(), serverBaseUrl, variablesFactory); } 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 e90d4b3c8..9de0fc429 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 @@ -24,6 +24,7 @@ import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Stream; import org.hl7.fhir.r4.model.Bundle; @@ -34,21 +35,23 @@ import org.hl7.fhir.r4.model.Resource; import org.springframework.beans.factory.InitializingBean; +import dev.dsf.bpe.v2.client.dsf.DsfClient; + public abstract class AbstractResourceProvider implements InitializingBean { - protected final DsfClientProvider clientProvider; + protected final Supplier localDsfClient; protected final String localEndpointAddress; - public AbstractResourceProvider(DsfClientProvider clientProvider, String localEndpointAddress) + public AbstractResourceProvider(Supplier localDsfClient, String localEndpointAddress) { - this.clientProvider = clientProvider; + this.localDsfClient = localDsfClient; this.localEndpointAddress = localEndpointAddress; } @Override public void afterPropertiesSet() throws Exception { - Objects.requireNonNull(clientProvider, "clientProvider"); + Objects.requireNonNull(localDsfClient, "localDsfClient"); Objects.requireNonNull(localEndpointAddress, "localEndpointAddress"); } @@ -98,6 +101,6 @@ private Bundle search(Class searchType, Map clientConfigsByFhirServerId = new HashMap<>(); private final KeyStore defaultTrustStore; - public FhirClientConfigProviderImpl(KeyStore defaultTrustStore, ClientConfigs clientConfigs) + public ClientConfigProviderImpl(KeyStore defaultTrustStore, ClientConfigs clientConfigs) { this.defaultTrustStore = defaultTrustStore; diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/FhirClientConfigProviderWithEndpointSupport.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/ClientConfigProviderWithEndpointSupport.java similarity index 92% rename from dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/FhirClientConfigProviderWithEndpointSupport.java rename to dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/ClientConfigProviderWithEndpointSupport.java index be3363c70..12ff5ef72 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/FhirClientConfigProviderWithEndpointSupport.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/ClientConfigProviderWithEndpointSupport.java @@ -25,13 +25,12 @@ import dev.dsf.bpe.api.config.FhirClientConfig; import dev.dsf.bpe.v2.client.fhir.ClientConfig; -public class FhirClientConfigProviderWithEndpointSupport implements FhirClientConfigProvider +public class ClientConfigProviderWithEndpointSupport implements ClientConfigProvider { private final EndpointProvider endpointProvider; - private final FhirClientConfigProvider delegate; + private final ClientConfigProvider delegate; - public FhirClientConfigProviderWithEndpointSupport(EndpointProvider endpointProvider, - FhirClientConfigProvider delegate) + public ClientConfigProviderWithEndpointSupport(EndpointProvider endpointProvider, ClientConfigProvider delegate) { this.endpointProvider = endpointProvider; this.delegate = delegate; diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/DsfClientProviderImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/DsfClientProviderImpl.java index d85027b2b..a22df0f38 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/DsfClientProviderImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/DsfClientProviderImpl.java @@ -18,36 +18,53 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; - +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.InitializingBean; import ca.uhn.fhir.context.FhirContext; import dev.dsf.bpe.api.config.BpeProxyConfig; import dev.dsf.bpe.api.config.DsfClientConfig; import dev.dsf.bpe.api.config.DsfClientConfig.BaseConfig; -import dev.dsf.bpe.api.service.BuildInfoProvider; import dev.dsf.bpe.v2.client.dsf.DsfClient; import dev.dsf.bpe.v2.client.dsf.DsfClientJersey; import dev.dsf.bpe.v2.client.dsf.ReferenceCleaner; +import dev.dsf.bpe.v2.client.fhir.ClientConfig; -public class DsfClientProviderImpl implements DsfClientProvider, InitializingBean +public class DsfClientProviderImpl implements DsfClientProvider, InitializingBean, DisposableBean { - private final Map webserviceClientsByUrl = new HashMap<>(); + private static final Logger logger = LoggerFactory.getLogger(DsfClientProviderImpl.class); + + private final Map clientsByUrlOrId = new HashMap<>(); private final FhirContext fhirContext; private final ReferenceCleaner referenceCleaner; private final DsfClientConfig dsfClientConfig; private final BpeProxyConfig proxyConfig; - private final BuildInfoProvider buildInfoProvider; + private final OidcClientProvider oidcClientProvider; + private final String userAgent; + private final ClientConfigProvider configProvider; + + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(8, + r -> new Thread(r, "dsf-client-async-scheduler")); public DsfClientProviderImpl(FhirContext fhirContext, ReferenceCleaner referenceCleaner, - DsfClientConfig dsfClientConfig, BpeProxyConfig proxyConfig, BuildInfoProvider buildInfoProvider) + DsfClientConfig dsfClientConfig, BpeProxyConfig proxyConfig, OidcClientProvider oidcClientProvider, + String userAgent, ClientConfigProvider configProvider) { this.fhirContext = fhirContext; this.referenceCleaner = referenceCleaner; this.dsfClientConfig = dsfClientConfig; this.proxyConfig = proxyConfig; - this.buildInfoProvider = buildInfoProvider; + this.oidcClientProvider = oidcClientProvider; + this.userAgent = userAgent; + this.configProvider = configProvider; } @Override @@ -57,15 +74,65 @@ public void afterPropertiesSet() throws Exception Objects.requireNonNull(referenceCleaner, "referenceCleaner"); Objects.requireNonNull(dsfClientConfig, "dsfClientConfig"); Objects.requireNonNull(proxyConfig, "proxyConfig"); - Objects.requireNonNull(buildInfoProvider, "buildInfoProvider"); + Objects.requireNonNull(oidcClientProvider, "oidcClientProvider"); + Objects.requireNonNull(userAgent, "userAgent"); + Objects.requireNonNull(configProvider, "configProvider"); + } + + @Override + public Optional getById(String fhirServerId) + { + return configProvider.getClientConfig(fhirServerId).flatMap(this::doGetById); } - private DsfClient getClient(String webserviceUrl) + private Optional doGetById(ClientConfig clientConfig) { - synchronized (webserviceClientsByUrl) + if (clientConfig == null) + return Optional.empty(); + + synchronized (clientsByUrlOrId) { - if (webserviceClientsByUrl.containsKey(webserviceUrl)) - return webserviceClientsByUrl.get(webserviceUrl); + DsfClientJersey client = clientsByUrlOrId.get(clientConfig.getFhirServerId()); + + if (client == null) + { + client = new DsfClientJersey(scheduler, clientConfig, oidcClientProvider, userAgent, fhirContext, + referenceCleaner); + clientsByUrlOrId.put(clientConfig.getFhirServerId(), client); + } + + return Optional.of(client); + } + } + + @Override + public DsfClient getLocal() + { + return getByEndpointUrl(dsfClientConfig.getLocalConfig().getBaseUrl()); + } + + @Override + public DsfClient getByEndpointUrl(String webserviceUrl) + { + Objects.requireNonNull(webserviceUrl, "webserviceUrl"); + + DsfClient cachedClient = clientsByUrlOrId.get(webserviceUrl); + if (cachedClient != null) + return cachedClient; + else + { + DsfClientJersey newClient = doGetByUrl(webserviceUrl); + clientsByUrlOrId.put(webserviceUrl, newClient); + return newClient; + } + } + + private DsfClientJersey doGetByUrl(String webserviceUrl) + { + synchronized (clientsByUrlOrId) + { + if (clientsByUrlOrId.containsKey(webserviceUrl)) + return clientsByUrlOrId.get(webserviceUrl); String proxyHost = null, proxyUsername = null; char[] proxyPassword = null; @@ -80,36 +147,34 @@ private DsfClient getClient(String webserviceUrl) ? dsfClientConfig.getLocalConfig() : dsfClientConfig.getRemoteConfig(); - DsfClient client = new DsfClientJersey(webserviceUrl, dsfClientConfig.getTrustStore(), + DsfClientJersey client = new DsfClientJersey(scheduler, webserviceUrl, dsfClientConfig.getTrustStore(), dsfClientConfig.getKeyStore(), dsfClientConfig.getKeyStorePassword(), proxyHost, proxyUsername, proxyPassword, config.getConnectTimeout(), config.getReadTimeout(), config.isDebugLoggingEnabled(), - buildInfoProvider.getUserAgentValue(), fhirContext, referenceCleaner); + userAgent, fhirContext, referenceCleaner); - webserviceClientsByUrl.put(webserviceUrl, client); + clientsByUrlOrId.put(webserviceUrl, client); return client; } } @Override - public DsfClient getLocalDsfClient() + public void destroy() throws Exception { - return getDsfClient(dsfClientConfig.getLocalConfig().getBaseUrl()); - } - - @Override - public DsfClient getDsfClient(String webserviceUrl) - { - Objects.requireNonNull(webserviceUrl, "webserviceUrl"); - - DsfClient cachedClient = webserviceClientsByUrl.get(webserviceUrl); - if (cachedClient != null) - return cachedClient; - else + scheduler.shutdown(); + try { - DsfClient newClient = getClient(webserviceUrl); - webserviceClientsByUrl.put(webserviceUrl, newClient); - return newClient; + if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) + { + scheduler.shutdownNow(); + if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) + logger.warn("DsfClientProvider scheduler did not terminate"); + } + } + catch (InterruptedException ie) + { + scheduler.shutdownNow(); + Thread.currentThread().interrupt(); } } } diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/EndpointProviderImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/EndpointProviderImpl.java index 1f40d5c89..1f9d4dfc4 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/EndpointProviderImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/EndpointProviderImpl.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Supplier; import java.util.stream.Stream; import org.hl7.fhir.r4.model.Bundle; @@ -31,19 +32,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import dev.dsf.bpe.v2.client.dsf.DsfClient; + public class EndpointProviderImpl extends AbstractResourceProvider implements EndpointProvider { private static final Logger logger = LoggerFactory.getLogger(EndpointProviderImpl.class); - public EndpointProviderImpl(DsfClientProvider clientProvider, String localEndpointAddress) + public EndpointProviderImpl(Supplier localDsfClient, String localEndpointAddress) { - super(clientProvider, localEndpointAddress); + super(localDsfClient, localEndpointAddress); } @Override public Optional getLocalEndpoint() { - Bundle resultBundle = clientProvider.getLocalDsfClient().searchWithStrictHandling(Endpoint.class, + Bundle resultBundle = localDsfClient.get().searchWithStrictHandling(Endpoint.class, Map.of("status", List.of("active"), "address", List.of(localEndpointAddress))); if (resultBundle == null || resultBundle.getEntry() == null || resultBundle.getEntry().size() != 1 @@ -74,7 +77,7 @@ public Optional getEndpoint(Identifier endpointIdentifier) String endpointIdSp = toSearchParameter(endpointIdentifier); - Bundle resultBundle = clientProvider.getLocalDsfClient().searchWithStrictHandling(Endpoint.class, + Bundle resultBundle = localDsfClient.get().searchWithStrictHandling(Endpoint.class, Map.of("status", List.of("active"), "identifier", List.of(endpointIdSp))); if (resultBundle == null || resultBundle.getEntry() == null || resultBundle.getTotal() != 1 @@ -112,7 +115,7 @@ else if (memberOrganizationRole == null) String memberOrganizationIdSp = toSearchParameter(memberOrganizationIdentifier); String memberOrganizationRoleSp = toSearchParameter(memberOrganizationRole); - Bundle resultBundle = clientProvider.getLocalDsfClient().searchWithStrictHandling(OrganizationAffiliation.class, + Bundle resultBundle = localDsfClient.get().searchWithStrictHandling(OrganizationAffiliation.class, Map.of("active", List.of("true"), "primary-organization:identifier", List.of(parentOrganizationIdSp), "participating-organization:identifier", List.of(memberOrganizationIdSp), "role", List.of(memberOrganizationRoleSp), "_include", List.of("OrganizationAffiliation:endpoint"))); diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/FhirClientProviderImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/FhirClientProviderImpl.java index f1dd75c6f..4573134bf 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/FhirClientProviderImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/FhirClientProviderImpl.java @@ -34,9 +34,9 @@ public class FhirClientProviderImpl implements FhirClientProvider, InitializingB private final ProxyConfig proxyConfig; private final OidcClientProvider oidcClientProvider; private final String userAgent; - private final FhirClientConfigProvider configProvider; + private final ClientConfigProvider configProvider; - private final Map clientFactoriesByFhirServerId = new HashMap<>(); + private final Map factoriesById = new HashMap<>(); /** * @param fhirContext @@ -51,7 +51,7 @@ public class FhirClientProviderImpl implements FhirClientProvider, InitializingB * not null */ public FhirClientProviderImpl(FhirContext fhirContext, ProxyConfig proxyConfig, - OidcClientProvider oidcClientProvider, String userAgent, FhirClientConfigProvider configProvider) + OidcClientProvider oidcClientProvider, String userAgent, ClientConfigProvider configProvider) { this.fhirContext = fhirContext; this.proxyConfig = proxyConfig; @@ -70,19 +70,19 @@ public void afterPropertiesSet() throws Exception Objects.requireNonNull(configProvider, "configProvider"); } - protected Optional getClient(ClientConfig clientConfig) + private Optional doGetById(ClientConfig clientConfig) { if (clientConfig == null) return Optional.empty(); - synchronized (clientFactoriesByFhirServerId) + synchronized (factoriesById) { - FhirClientFactory factory = clientFactoriesByFhirServerId.get(clientConfig.getFhirServerId()); + FhirClientFactory factory = factoriesById.get(clientConfig.getFhirServerId()); if (factory == null) { factory = createClientFactory(clientConfig); - clientFactoriesByFhirServerId.put(clientConfig.getFhirServerId(), factory); + factoriesById.put(clientConfig.getFhirServerId(), factory); } return Optional.of(factory.newGenericClient(clientConfig.getBaseUrl())); @@ -90,9 +90,9 @@ protected Optional getClient(ClientConfig clientConfig) } @Override - public Optional getClient(String fhirServerId) + public Optional getById(String fhirServerId) { - return configProvider.getClientConfig(fhirServerId).flatMap(this::getClient); + return configProvider.getClientConfig(fhirServerId).flatMap(this::doGetById); } protected FhirClientFactory createClientFactory(ClientConfig config) diff --git a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/OrganizationProviderImpl.java b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/OrganizationProviderImpl.java index 5fefdaede..c597951dd 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/OrganizationProviderImpl.java +++ b/dsf-bpe/dsf-bpe-process-api-v2-impl/src/main/java/dev/dsf/bpe/v2/service/OrganizationProviderImpl.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Supplier; import java.util.stream.Stream; import org.hl7.fhir.r4.model.Bundle; @@ -31,19 +32,21 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import dev.dsf.bpe.v2.client.dsf.DsfClient; + public class OrganizationProviderImpl extends AbstractResourceProvider implements OrganizationProvider { private static final Logger logger = LoggerFactory.getLogger(OrganizationProviderImpl.class); - public OrganizationProviderImpl(DsfClientProvider clientProvider, String localEndpointAddress) + public OrganizationProviderImpl(Supplier localDsfClient, String localEndpointAddress) { - super(clientProvider, localEndpointAddress); + super(localDsfClient, localEndpointAddress); } @Override public Optional getLocalOrganization() { - Bundle resultBundle = clientProvider.getLocalDsfClient().searchWithStrictHandling(Endpoint.class, + Bundle resultBundle = localDsfClient.get().searchWithStrictHandling(Endpoint.class, Map.of("status", List.of("active"), "address", List.of(localEndpointAddress), "_include", List.of("Endpoint:organization"))); @@ -85,7 +88,7 @@ public Optional getOrganization(Identifier organizationIdentifier) String organizationIdSp = toSearchParameter(organizationIdentifier); - Bundle resultBundle = clientProvider.getLocalDsfClient().searchWithStrictHandling(Organization.class, + Bundle resultBundle = localDsfClient.get().searchWithStrictHandling(Organization.class, Map.of("active", List.of("true"), "identifier", List.of(organizationIdSp))); if (resultBundle == null || resultBundle.getEntry() == null || resultBundle.getTotal() != 1 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 index 062551521..59bfd8518 100644 --- 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 @@ -24,6 +24,7 @@ import java.util.Objects; import java.util.Set; import java.util.UUID; +import java.util.function.Supplier; import java.util.stream.Stream; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; @@ -34,6 +35,7 @@ import org.hl7.fhir.r4.model.Organization; import org.hl7.fhir.r4.model.OrganizationAffiliation; +import dev.dsf.bpe.v2.client.dsf.DsfClient; import dev.dsf.bpe.v2.constants.NamingSystems.EndpointIdentifier; import dev.dsf.bpe.v2.constants.NamingSystems.OrganizationIdentifier; import dev.dsf.bpe.v2.variables.Target; @@ -131,9 +133,9 @@ public Builder filter(Predicate filter) } } - public TargetProviderImpl(DsfClientProvider clientProvider, String localEndpointAddress) + public TargetProviderImpl(Supplier localDsfClient, String localEndpointAddress) { - super(clientProvider, localEndpointAddress); + super(localDsfClient, localEndpointAddress); } protected BuilderImpl createBuilder(Identifier... 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 8a01479b7..66e76bad0 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 @@ -17,6 +17,7 @@ import java.util.Locale; import java.util.function.Function; +import java.util.function.Supplier; import org.apache.tika.detect.Detector; import org.operaton.bpm.engine.delegate.DelegateExecution; @@ -24,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Lazy; import com.fasterxml.jackson.databind.ObjectMapper; @@ -38,6 +40,7 @@ import dev.dsf.bpe.api.service.BpeMailService; import dev.dsf.bpe.api.service.BpeOidcClientProvider; import dev.dsf.bpe.api.service.BuildInfoProvider; +import dev.dsf.bpe.v2.client.dsf.DsfClient; import dev.dsf.bpe.v2.client.dsf.ReferenceCleaner; import dev.dsf.bpe.v2.client.dsf.ReferenceCleanerImpl; import dev.dsf.bpe.v2.client.dsf.ReferenceExtractor; @@ -51,6 +54,9 @@ import dev.dsf.bpe.v2.listener.ListenerVariables; import dev.dsf.bpe.v2.listener.StartListener; import dev.dsf.bpe.v2.plugin.ProcessPluginFactoryImpl; +import dev.dsf.bpe.v2.service.ClientConfigProvider; +import dev.dsf.bpe.v2.service.ClientConfigProviderImpl; +import dev.dsf.bpe.v2.service.ClientConfigProviderWithEndpointSupport; import dev.dsf.bpe.v2.service.CompressionService; import dev.dsf.bpe.v2.service.CompressionServiceImpl; import dev.dsf.bpe.v2.service.CryptoService; @@ -61,9 +67,6 @@ import dev.dsf.bpe.v2.service.DsfClientProviderImpl; import dev.dsf.bpe.v2.service.EndpointProvider; import dev.dsf.bpe.v2.service.EndpointProviderImpl; -import dev.dsf.bpe.v2.service.FhirClientConfigProvider; -import dev.dsf.bpe.v2.service.FhirClientConfigProviderImpl; -import dev.dsf.bpe.v2.service.FhirClientConfigProviderWithEndpointSupport; import dev.dsf.bpe.v2.service.FhirClientProvider; import dev.dsf.bpe.v2.service.FhirClientProviderImpl; import dev.dsf.bpe.v2.service.MailService; @@ -129,7 +132,7 @@ public ProxyConfig proxyConfigDelegate() @Bean public EndpointProvider endpointProvider() { - return new EndpointProviderImpl(dsfClientProvider(), dsfClientConfig.getLocalConfig().getBaseUrl()); + return new EndpointProviderImpl(getLocal(), dsfClientConfig.getLocalConfig().getBaseUrl()); } @Bean @@ -148,11 +151,19 @@ public Locale getLocale() return context; } + @Bean + @Lazy + public Supplier getLocal() + { + // lazy and indirect to break dependency cycle + return () -> dsfClientProvider().getLocal(); + } + @Bean public DsfClientProvider dsfClientProvider() { return new DsfClientProviderImpl(fhirContext(), referenceCleaner(), dsfClientConfig, proxyConfig, - buildInfoProvider); + oidcClientProvider(), buildInfoProvider.getUserAgentValue(), fhirClientConfigProvider()); } @Bean @@ -163,10 +174,10 @@ public FhirClientProvider fhirClientProvider() } @Bean - public FhirClientConfigProvider fhirClientConfigProvider() + public ClientConfigProvider fhirClientConfigProvider() { - return new FhirClientConfigProviderWithEndpointSupport(endpointProvider(), - new FhirClientConfigProviderImpl(fhirClientConfigs.defaultTrustStore(), clientConfigsDelegate())); + return new ClientConfigProviderWithEndpointSupport(endpointProvider(), + new ClientConfigProviderImpl(fhirClientConfigs.defaultTrustStore(), clientConfigsDelegate())); } @Bean @@ -197,7 +208,7 @@ public ObjectMapper objectMapper() @Bean public OrganizationProvider organizationProvider() { - return new OrganizationProviderImpl(dsfClientProvider(), dsfClientConfig.getLocalConfig().getBaseUrl()); + return new OrganizationProviderImpl(getLocal(), dsfClientConfig.getLocalConfig().getBaseUrl()); } @Bean @@ -275,7 +286,7 @@ public JsonHolderSerializer jsonVariableSerializer() @Bean public Function listenerVariablesFactory() { - return execution -> new VariablesImpl(execution, objectMapper(), dsfClientProvider().getLocalDsfClient()); + return execution -> new VariablesImpl(execution, objectMapper(), dsfClientProvider().getLocal()); } @Bean @@ -288,7 +299,7 @@ public ExecutionListener startListener() public ExecutionListener endListener() { return new EndListener(dsfClientConfig.getLocalConfig().getBaseUrl(), listenerVariablesFactory(), - dsfClientProvider().getLocalDsfClient()); + dsfClientProvider().getLocal()); } @Bean @@ -319,7 +330,7 @@ public CryptoService cryptoService() @Bean public TargetProvider targetProvider() { - return new TargetProviderImpl(dsfClientProvider(), dsfClientConfig.getLocalConfig().getBaseUrl()); + return new TargetProviderImpl(getLocal(), dsfClientConfig.getLocalConfig().getBaseUrl()); } @Bean 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 cce203d6c..cfe1da2c1 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 @@ -22,12 +22,12 @@ import ca.uhn.fhir.context.FhirContext; import dev.dsf.bpe.v2.config.ProxyConfig; +import dev.dsf.bpe.v2.service.ClientConfigProvider; import dev.dsf.bpe.v2.service.CompressionService; import dev.dsf.bpe.v2.service.CryptoService; import dev.dsf.bpe.v2.service.DataLogger; import dev.dsf.bpe.v2.service.DsfClientProvider; import dev.dsf.bpe.v2.service.EndpointProvider; -import dev.dsf.bpe.v2.service.FhirClientConfigProvider; import dev.dsf.bpe.v2.service.FhirClientProvider; import dev.dsf.bpe.v2.service.MailService; import dev.dsf.bpe.v2.service.MimeTypeService; @@ -61,7 +61,7 @@ public interface ProcessPluginApi FhirClientProvider getFhirClientProvider(); - FhirClientConfigProvider getFhirClientConfigProvider(); + ClientConfigProvider getFhirClientConfigProvider(); OidcClientProvider getOidcClientProvider(); diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/DefaultUserTaskListener.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/DefaultUserTaskListener.java index d5a6fe7c4..03131bba6 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/DefaultUserTaskListener.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/DefaultUserTaskListener.java @@ -234,12 +234,12 @@ public void notify(ProcessPluginApi api, Variables variables, protected QuestionnaireResponse createQuestionnaireResponse(ProcessPluginApi api, QuestionnaireResponse questionnaireResponse) { - return api.getDsfClientProvider().getLocalDsfClient().create(questionnaireResponse); + return api.getDsfClientProvider().getLocal().create(questionnaireResponse); } private Questionnaire readQuestionnaire(ProcessPluginApi api, String urlWithVersion) { - Bundle search = api.getDsfClientProvider().getLocalDsfClient().search(Questionnaire.class, + Bundle search = api.getDsfClientProvider().getLocal().search(Questionnaire.class, Map.of("url", List.of(urlWithVersion))); List questionnaires = search.getEntry().stream().filter(Bundle.BundleEntryComponent::hasResource) diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/DefaultTaskSender.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/DefaultTaskSender.java index bef2aa9be..9faafe846 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/DefaultTaskSender.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/activity/task/DefaultTaskSender.java @@ -95,7 +95,7 @@ public void send() protected IdType doSend(Task task, String targetEndpointUrl) { - return api.getDsfClientProvider().getDsfClient(targetEndpointUrl).withMinimalReturn().create(task); + return api.getDsfClientProvider().getByEndpointUrl(targetEndpointUrl).withMinimalReturn().create(task); } protected TaskAndConfig createTaskAndConfig(BusinessKeyStrategy businessKeyStrategy) diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/AsyncDsfClient.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/AsyncDsfClient.java new file mode 100644 index 000000000..e60d104df --- /dev/null +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/AsyncDsfClient.java @@ -0,0 +1,340 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.bpe.v2.client.dsf; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Resource; + +public interface AsyncDsfClient +{ + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF}.
+ *
+ * Send "Prefer: respond-async" header and handles async response + * + * @param resourceType + * not null + * @param parameters + * may be null + * @return async search result + */ + default CompletableFuture searchAsync(Class resourceType, + Map> parameters) + { + return searchAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, parameters); + } + + /** + * Send "Prefer: respond-async" header and handles async response + * + * @param delayStrategy + * not null + * @param resourceType + * not null + * @param parameters + * may be null + * @return async search result + */ + CompletableFuture searchAsync(DelayStrategy delayStrategy, Class resourceType, + Map> parameters); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async" header and handles async response + * + * @param url + * not null, not empty, expected to contain path with a valid FHIR resource name and + * optional query parameters + * @return async search result + */ + default CompletableFuture searchAsync(String url) + { + return searchAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, url); + } + + /** + * Send "Prefer: respond-async" header and handles async response + * + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param url + * not null, not empty, expected to contain path with a valid FHIR resource name and + * optional query parameters + * @return async search result + */ + CompletableFuture searchAsync(DelayStrategy delayStrategy, String url); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, handling=strict" header and handles async response + * + * @param resourceType + * not null + * @param parameters + * may be null + * @return async search result + */ + default CompletableFuture searchAsyncWithStrictHandling(Class resourceType, + Map> parameters) + { + return searchAsyncWithStrictHandling(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, parameters); + } + + /** + * Send "Prefer: respond-async, handling=strict" header and handles async response + * + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param parameters + * may be null + * @return async search result + */ + CompletableFuture searchAsyncWithStrictHandling(DelayStrategy delayStrategy, + Class resourceType, Map> parameters); + + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, handling=strict" header and handles async response + * + * @param url + * not null, not empty, expected to contain path with a valid FHIR resource name and + * optional query parameters + * @return async search result + */ + default CompletableFuture searchAsyncWithStrictHandling(String url) + { + return searchAsyncWithStrictHandling(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, url); + } + + /** + * Send "Prefer: respond-async, handling=strict" header and handles async response + * + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param url + * not null, not empty, expected to contain path with a valid FHIR resource name and + * optional query parameters + * @return async search result + */ + CompletableFuture searchAsyncWithStrictHandling(DelayStrategy delayStrategy, String url); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=representation" header and handles async response + * + * @param + * return resource type + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @param returnType + * not null + * @return async operation result + */ + default CompletableFuture operationAsync(String operationName, Parameters parameters, + Class returnType) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, operationName, parameters, returnType); + } + + /** + * Send "Prefer: respond-async, return=representation" header and handles async response + * + * @param + * return resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @param returnType + * not null + * @return async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, String operationName, + Parameters parameters, Class returnType); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=representation" header and handles async response + * + * @param + * return resource type + * @param + * request path resource type + * @param resourceType + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @param returnType + * not null + * @return async operation result + */ + default CompletableFuture operationAsync(Class resourceType, + String operationName, Parameters parameters, Class returnType) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, operationName, parameters, + returnType); + } + + /** + * Send "Prefer: respond-async, return=representation" header and handles async response + * + * @param + * return resource type + * @param + * request path resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @param returnType + * not null + * @return async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String operationName, Parameters parameters, Class returnType); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=representation" header and handles async response + * + * @param + * return resource type + * @param + * request path resource type + * @param resourceType + * not null + * @param id + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @param returnType + * not null + * @return async operation result + */ + default CompletableFuture operationAsync(Class resourceType, + String id, String operationName, Parameters parameters, Class returnType) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, id, operationName, parameters, + returnType); + } + + /** + * Send "Prefer: respond-async, return=representation" header and handles async response + * + * @param + * return resource type + * @param + * request path resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param id + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @param returnType + * not null + * @return async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String operationName, Parameters parameters, Class returnType); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=representation" header and handles async response + * + * @param + * return resource type + * @param + * request path resource type + * @param resourceType + * not null + * @param id + * not null + * @param version + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @param returnType + * not null + * @return async operation result + */ + default CompletableFuture operationAsync(Class resourceType, + String id, String version, String operationName, Parameters parameters, Class returnType) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, id, version, operationName, + parameters, returnType); + } + + /** + * Send "Prefer: respond-async, return=representation" header and handles async response + * + * @param + * return resource type + * @param + * request path resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param id + * not null + * @param version + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @param returnType + * not null + * @return async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String version, String operationName, Parameters parameters, + Class returnType); +} diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/AsyncPreferReturnMinimal.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/AsyncPreferReturnMinimal.java new file mode 100644 index 000000000..09ca9c8c9 --- /dev/null +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/AsyncPreferReturnMinimal.java @@ -0,0 +1,184 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.bpe.v2.client.dsf; + +import java.util.concurrent.CompletableFuture; + +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Resource; + +public interface AsyncPreferReturnMinimal +{ + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=minimal" header and handles async response + * + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return location value from async operation result + */ + default CompletableFuture operationAsync(String operationName, Parameters parameters) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, operationName, parameters); + } + + /** + * Send "Prefer: respond-async, return=minimal" header and handles async response + * + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return location value from async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, String operationName, Parameters parameters); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=minimal" header and handles async response + * + * @param + * request path resource type + * @param resourceType + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return location value from async operation result + */ + default CompletableFuture operationAsync(Class resourceType, String operationName, + Parameters parameters) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, operationName, parameters); + } + + /** + * Send "Prefer: respond-async, return=minimal" header and handles async response + * + * @param + * request path resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return location value from async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, Class resourceType, + String operationName, Parameters parameters); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=minimal" header and handles async response + * + * @param + * request path resource type + * @param resourceType + * not null + * @param id + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return location value from async operation result + */ + default CompletableFuture operationAsync(Class resourceType, String id, + String operationName, Parameters parameters) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, id, operationName, parameters); + } + + /** + * Send "Prefer: respond-async, return=minimal" header and handles async response + * + * @param + * request path resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param id + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return location value from async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, Class resourceType, + String id, String operationName, Parameters parameters); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=minimal" header and handles async response + * + * @param + * request path resource type + * @param resourceType + * not null + * @param id + * not null + * @param version + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return location value from async operation result + */ + default CompletableFuture operationAsync(Class resourceType, String id, + String version, String operationName, Parameters parameters) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, id, version, operationName, + parameters); + } + + /** + * Send "Prefer: respond-async, return=minimal" header and handles async response + * + * @param + * request path resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param id + * not null + * @param version + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return location value from async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, Class resourceType, + String id, String version, String operationName, Parameters parameters); +} \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/AsyncPreferReturnOutcome.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/AsyncPreferReturnOutcome.java new file mode 100644 index 000000000..ab5e7b6b2 --- /dev/null +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/AsyncPreferReturnOutcome.java @@ -0,0 +1,185 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.bpe.v2.client.dsf; + +import java.util.concurrent.CompletableFuture; + +import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Parameters; +import org.hl7.fhir.r4.model.Resource; + +public interface AsyncPreferReturnOutcome +{ + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=OperationOutcome" header and handles async response + * + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return OperationOutcome from async operation result + */ + default CompletableFuture operationAsync(String operationName, Parameters parameters) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, operationName, parameters); + } + + /** + * Send "Prefer: respond-async, return=OperationOutcome" header and handles async response + * + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return OperationOutcome from async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, String operationName, + Parameters parameters); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=OperationOutcome" header and handles async response + * + * @param + * request path resource type + * @param resourceType + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return OperationOutcome from async operation result + */ + default CompletableFuture operationAsync(Class resourceType, + String operationName, Parameters parameters) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, operationName, parameters); + } + + /** + * Send "Prefer: respond-async, return=OperationOutcome" header and handles async response + * + * @param + * request path resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return OperationOutcome from async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String operationName, Parameters parameters); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=OperationOutcome" header and handles async response + * + * @param + * request path resource type + * @param resourceType + * not null + * @param id + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return OperationOutcome from async operation result + */ + default CompletableFuture operationAsync(Class resourceType, String id, + String operationName, Parameters parameters) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, id, operationName, parameters); + } + + /** + * Send "Prefer: respond-async, return=OperationOutcome" header and handles async response + * + * @param + * request path resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param id + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return OperationOutcome from async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String operationName, Parameters parameters); + + /** + * Uses {@link DelayStrategy#TRUNCATED_EXPONENTIAL_BACKOFF} unless the server sends Retry-After headers.
+ *
+ * Send "Prefer: respond-async, return=OperationOutcome" header and handles async response + * + * @param + * request path resource type + * @param resourceType + * not null + * @param id + * not null + * @param version + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return OperationOutcome from async operation result + */ + default CompletableFuture operationAsync(Class resourceType, String id, + String version, String operationName, Parameters parameters) + { + return operationAsync(DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF, resourceType, id, version, operationName, + parameters); + } + + /** + * Send "Prefer: respond-async, return=OperationOutcome" header and handles async response + * + * @param + * request path resource type + * @param delayStrategy + * not null, will be ignored if the server sends Retry-After headers + * @param resourceType + * not null + * @param id + * not null + * @param version + * not null + * @param operationName + * not null, $ will be prepended if not present + * @param parameters + * may be null + * @return OperationOutcome from async operation result + */ + CompletableFuture operationAsync(DelayStrategy delayStrategy, + Class resourceType, String id, String version, String operationName, Parameters parameters); +} \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/BasicDsfClient.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/BasicDsfClient.java index 0bed561f8..4ad124fe2 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/BasicDsfClient.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/BasicDsfClient.java @@ -171,8 +171,24 @@ BinaryInputStream readBinary(String id, String version, MediaType mediaType, Lon boolean exists(IdType resourceTypeIdVersion); + /** + * @param resourceType + * not null + * @param parameters + * may be null + * @return + */ Bundle search(Class resourceType, Map> parameters); + /** + * Send "Prefer: handling=strict" header + * + * @param resourceType + * not null + * @param parameters + * may be null + * @return search result + */ Bundle searchWithStrictHandling(Class resourceType, Map> parameters); CapabilityStatement getConformance(); diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/DelayStrategy.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/DelayStrategy.java new file mode 100644 index 000000000..d80d32b99 --- /dev/null +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/DelayStrategy.java @@ -0,0 +1,79 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.bpe.v2.client.dsf; + +import java.time.Duration; + +public interface DelayStrategy +{ + /** + * Waits for 100ms, 200ms, 400ms, 800ms, 800ms, ... + */ + DelayStrategy TRUNCATED_EXPONENTIAL_BACKOFF = new DelayStrategy() + { + @Override + public Duration getFirstDelay() + { + return Duration.ofMillis(100); + } + + @Override + public Duration getNextDelay(Duration lastDelay) + { + if (Duration.ofMillis(800).compareTo(lastDelay) <= 0) + return lastDelay; + + return lastDelay.multipliedBy(2); + } + }; + + /** + * Waits for 200ms, 200ms, ... + */ + DelayStrategy CONSTANT = constant(Duration.ofMillis(200)); + + /** + * @param delay + * not null, not {@link Duration#isNegative()} + * @return constant strategy with the given interval between tries + * @throws IllegalArgumentException + * if given delay is null or {@link Duration#isNegative()} + */ + static DelayStrategy constant(Duration delay) + { + if (delay == null || delay.isNegative()) + throw new IllegalArgumentException("delay null or negative"); + + return new DelayStrategy() + { + @Override + public Duration getFirstDelay() + { + return delay; + } + + @Override + public Duration getNextDelay(Duration lastInterval) + { + return delay; + } + }; + } + + Duration getFirstDelay(); + + Duration getNextDelay(Duration lastDelay); +} \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/DsfClient.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/DsfClient.java index 5d6afcb58..bc256f8d7 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/DsfClient.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/DsfClient.java @@ -15,7 +15,13 @@ */ package dev.dsf.bpe.v2.client.dsf; -public interface DsfClient extends BasicDsfClient, RetryClient +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.WebApplicationException; + +/** + * All request methods may throw {@link WebApplicationException} and {@link ProcessingException} + */ +public interface DsfClient extends BasicDsfClient, AsyncDsfClient, RetryClient { String getBaseUrl(); diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimal.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimal.java index 26fe03e16..d95bb60ad 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimal.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimal.java @@ -21,6 +21,7 @@ import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import jakarta.ws.rs.core.MediaType; @@ -40,4 +41,14 @@ public interface PreferReturnMinimal IdType updateBinary(String id, InputStream in, MediaType mediaType, String securityContextReference); Bundle postBundle(Bundle bundle); + + IdType operation(String operationName, Parameters parameters); + + IdType operation(Class resourceType, String operationName, Parameters parameters); + + IdType operation(Class resourceType, String id, String operationName, + Parameters parameters); + + IdType operation(Class resourceType, String id, String version, String operationName, + Parameters parameters); } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalWithRetry.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalWithRetry.java index f95dc0b0b..43c7a2ace 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalWithRetry.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnMinimalWithRetry.java @@ -15,6 +15,7 @@ */ package dev.dsf.bpe.v2.client.dsf; -public interface PreferReturnMinimalWithRetry extends PreferReturnMinimal, RetryClient +public interface PreferReturnMinimalWithRetry + extends PreferReturnMinimal, AsyncPreferReturnMinimal, RetryClient { } diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcome.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcome.java index 9b0d5dda4..fbb5c5d28 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcome.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcome.java @@ -21,6 +21,7 @@ import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.OperationOutcome; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import jakarta.ws.rs.core.MediaType; @@ -33,13 +34,21 @@ public interface PreferReturnOutcome OperationOutcome createBinary(InputStream in, MediaType mediaType, String securityContextReference); - OperationOutcome update(Resource resource); OperationOutcome updateConditionaly(Resource resource, Map> criteria); OperationOutcome updateBinary(String id, InputStream in, MediaType mediaType, String securityContextReference); - Bundle postBundle(Bundle bundle); + + OperationOutcome operation(String operationName, Parameters parameters); + + OperationOutcome operation(Class resourceType, String operationName, Parameters parameters); + + OperationOutcome operation(Class resourceType, String id, String operationName, + Parameters parameters); + + OperationOutcome operation(Class resourceType, String id, String version, + String operationName, Parameters parameters); } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeWithRetry.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeWithRetry.java index b64365bea..33c5a632f 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeWithRetry.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnOutcomeWithRetry.java @@ -15,6 +15,7 @@ */ package dev.dsf.bpe.v2.client.dsf; -public interface PreferReturnOutcomeWithRetry extends PreferReturnOutcome, RetryClient +public interface PreferReturnOutcomeWithRetry + extends PreferReturnOutcome, AsyncPreferReturnOutcome, RetryClient { } diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnResource.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnResource.java index e634a2278..c4c12251a 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnResource.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/PreferReturnResource.java @@ -21,6 +21,7 @@ import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Parameters; import org.hl7.fhir.r4.model.Resource; import jakarta.ws.rs.core.MediaType; @@ -33,13 +34,22 @@ public interface PreferReturnResource Binary createBinary(InputStream in, MediaType mediaType, String securityContextReference); - R update(R resource); R updateConditionaly(R resource, Map> criteria); Binary updateBinary(String id, InputStream in, MediaType mediaType, String securityContextReference); - Bundle postBundle(Bundle bundle); + + R operation(String operationName, Parameters parameters, Class returnType); + + R operation(Class resourceType, String operationName, + Parameters parameters, Class returnType); + + R operation(Class resourceType, String id, String operationName, + Parameters parameters, Class returnType); + + R operation(Class resourceType, String id, String version, + String operationName, Parameters parameters, Class returnType); } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/RetryClient.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/RetryClient.java index 9dcb49ce2..420d701bb 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/RetryClient.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/client/dsf/RetryClient.java @@ -21,7 +21,8 @@ public interface RetryClient { int RETRY_ONCE = 1; int RETRY_FOREVER = -1; - Duration FIVE_SECONDS = Duration.ofSeconds(5); + + DelayStrategy FIVE_SECONDS = DelayStrategy.constant(Duration.ofSeconds(5)); /** * retries once after a delay of {@link RetryClient#FIVE_SECONDS} @@ -59,27 +60,27 @@ default T withRetry(int nTimes) */ default T withRetry(Duration delay) { - return withRetry(RETRY_ONCE, delay); + return withRetry(RETRY_ONCE, DelayStrategy.constant(delay)); } /** * @param nTimes * {@code >= 0} - * @param delay - * not null, not {@link Duration#isNegative()} + * @param delayStrategy + * not null * @return T * * @throws IllegalArgumentException * if given nTimes or delay is null or {@link Duration#isNegative()} */ - T withRetry(int nTimes, Duration delay); + T withRetry(int nTimes, DelayStrategy delayStrategy); /** - * @param delay - * not null, not {@link Duration#isNegative()} + * @param delayStrategy + * not null * @return T * @throws IllegalArgumentException * if given delay is null or {@link Duration#isNegative()} */ - T withRetryForever(Duration delay); + T withRetryForever(DelayStrategy delayStrategy); } diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/constants/CodeSystems.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/constants/CodeSystems.java index 5acd55cde..83ebae474 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/constants/CodeSystems.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/constants/CodeSystems.java @@ -469,62 +469,80 @@ private Codes() public static final String AMS_USER = "AMS_USER"; public static final String ASP_USER = "ASP_USER"; public static final String SPR_USER = "SPR_USER"; + public static final String TSP_USER = "TSP_USER"; + public static final String PPH_USER = "PPH_USER"; + public static final String BIO_USER = "BIO_USER"; public static final String DSF_ADMIN = "DSF_ADMIN"; } public static final Coding uacUser() { - return new Coding(SYSTEM, Codes.UAC_USER, "Use-and-Access Committee Member"); + return new Coding(SYSTEM, Codes.UAC_USER, "Use-and-Access Committee User"); } public static final Coding cosUser() { - return new Coding(SYSTEM, Codes.COS_USER, "Coordinating Site Member"); + return new Coding(SYSTEM, Codes.COS_USER, "Coordinating Site User"); } public static final Coding crrUser() { - return new Coding(SYSTEM, Codes.CRR_USER, "Central Research Repository Member"); + return new Coding(SYSTEM, Codes.CRR_USER, "Central Research Repository User"); } public static final Coding dicUser() { - return new Coding(SYSTEM, Codes.DIC_USER, "Data Integration Center Member"); + return new Coding(SYSTEM, Codes.DIC_USER, "Data Integration Center User"); } public static final Coding dmsUser() { - return new Coding(SYSTEM, Codes.DMS_USER, "Data Management Site Member"); + return new Coding(SYSTEM, Codes.DMS_USER, "Data Management Site User"); } public static final Coding dtsUser() { - return new Coding(SYSTEM, Codes.DTS_USER, "Data Transfer Site Member"); + return new Coding(SYSTEM, Codes.DTS_USER, "Data Transfer Site User"); } public static final Coding hrpUser() { - return new Coding(SYSTEM, Codes.HRP_USER, "Health Research Platform Member"); + return new Coding(SYSTEM, Codes.HRP_USER, "Health Research Platform User"); } public static final Coding ttpUser() { - return new Coding(SYSTEM, Codes.TTP_USER, "Trusted Third Party Member"); + return new Coding(SYSTEM, Codes.TTP_USER, "Trusted Third Party User"); } public static final Coding amsUser() { - return new Coding(SYSTEM, Codes.AMS_USER, "Allowlist Management Site Member"); + return new Coding(SYSTEM, Codes.AMS_USER, "Allowlist Management Site User"); } public static final Coding aspUser() { - return new Coding(SYSTEM, Codes.ASP_USER, "Analysis Service Provider Member"); + return new Coding(SYSTEM, Codes.ASP_USER, "Analysis Service Provider User"); } public static final Coding sprUser() { - return new Coding(SYSTEM, Codes.SPR_USER, "Service Provider Registry Member"); + return new Coding(SYSTEM, Codes.SPR_USER, "Service Provider Registry User"); + } + + public static final Coding tspUser() + { + return new Coding(SYSTEM, Codes.TSP_USER, "Terminology Service Provider User"); + } + + public static final Coding pphUser() + { + return new Coding(SYSTEM, Codes.PPH_USER, "Process Plugin Hub User"); + } + + public static final Coding bioUser() + { + return new Coding(SYSTEM, Codes.BIO_USER, "Biobank User"); } public static final Coding dsfAdmin() @@ -587,6 +605,21 @@ public static final boolean isSprUser(Coding coding) return isSame(SYSTEM, Codes.SPR_USER, coding); } + public static final boolean isTspUser(Coding coding) + { + return isSame(SYSTEM, Codes.TSP_USER, coding); + } + + public static final boolean isPphUser(Coding coding) + { + return isSame(SYSTEM, Codes.PPH_USER, coding); + } + + public static final boolean isBioUser(Coding coding) + { + return isSame(SYSTEM, Codes.BIO_USER, coding); + } + public static final boolean isDsfAdmin(Coding coding) { return isSame(SYSTEM, Codes.DSF_ADMIN, coding); diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/error/impl/AbstractErrorHandler.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/error/impl/AbstractErrorHandler.java index bc72e5bb1..4a139c8bd 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/error/impl/AbstractErrorHandler.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/error/impl/AbstractErrorHandler.java @@ -131,7 +131,7 @@ protected void updateTaskAndHandleException(ProcessPluginApi api, Variables vari logger.debug("Updating Task {}, new status: {}", api.getTaskHelper().getLocalVersionlessAbsoluteUrl(task), task.getStatus().toCode()); - api.getDsfClientProvider().getLocalDsfClient().withMinimalReturn().update(task); + api.getDsfClientProvider().getLocal().withMinimalReturn().update(task); } catch (Exception e) { diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/FhirClientConfigProvider.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/ClientConfigProvider.java similarity index 98% rename from dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/FhirClientConfigProvider.java rename to dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/ClientConfigProvider.java index 2637f8faf..dd3d4e55b 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/FhirClientConfigProvider.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/ClientConfigProvider.java @@ -31,7 +31,7 @@ * `dev.dsf.bpe.fhir.client.connections.config.default.trust.server.certificate.cas` as default for the YAML properties * `trusted-root-certificates-file` and `oidc-auth.trusted-root-certificates-file` */ -public interface FhirClientConfigProvider +public interface ClientConfigProvider { /** * Every call to this method creates a new {@link SSLContext} object. diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/DsfClientProvider.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/DsfClientProvider.java index 2e0e6a05e..30573fa6c 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/DsfClientProvider.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/DsfClientProvider.java @@ -16,36 +16,54 @@ package dev.dsf.bpe.v2.service; import java.util.Objects; +import java.util.Optional; import org.hl7.fhir.r4.model.Endpoint; import dev.dsf.bpe.v2.client.dsf.DsfClient; +import dev.dsf.bpe.v2.constants.NamingSystems; /** - * Provides clients for DSF FHIR servers. + * Provides DSF clients for configured (non DSF) FHIR servers and DSF FHIR servers. * * @see FhirClientProvider + * @see ClientConfigProvider */ public interface DsfClientProvider { - DsfClient getLocalDsfClient(); + /** + * DSF client for a FHIR server configured via YAML with the given fhirServerId.
+ *
+ * Use #local as the fhirServerId for a connection to the local DSF FHIR server.
+ * Use #<value> as the fhirServerId for a connection to a DSF FHIR server with an active + * {@link Endpoint} resource and the given fhirServerId as the {@value NamingSystems.EndpointIdentifier#SID} + * value (ignoring the {@literal #} character). + * + * @param fhirServerId + * may be null + * @return never null, {@link Optional#empty()} if no client is configured for the given + * fhirServerId + */ + Optional getById(String fhirServerId); + + DsfClient getLocal(); /** * @param webserviceUrl * not null * @return {@link DsfClient} for the given webserviceUrl */ - DsfClient getDsfClient(String webserviceUrl); + DsfClient getByEndpointUrl(String webserviceUrl); /** * @param endpoint * not null, endpoint.address not null * @return {@link DsfClient} for the address defined in the given endpoint */ - default DsfClient getDsfClient(Endpoint endpoint) + default DsfClient getByEndpoint(Endpoint endpoint) { Objects.requireNonNull(endpoint, "endpoint"); - return getDsfClient(endpoint.getAddress()); + return getByEndpointUrl(endpoint.getAddress()); } } \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/FhirClientProvider.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/FhirClientProvider.java index a56081345..6b8ddf7a6 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/FhirClientProvider.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/FhirClientProvider.java @@ -23,10 +23,10 @@ import dev.dsf.bpe.v2.constants.NamingSystems; /** - * Provides connection configurations and HAPI FHIR clients for configured (non DSF) FHIR servers and DSF FHIR servers. + * Provides HAPI FHIR clients for configured (non DSF) FHIR servers and DSF FHIR servers. * * @see DsfClientProvider - * @see FhirClientConfigProvider + * @see ClientConfigProvider */ public interface FhirClientProvider { @@ -42,7 +42,6 @@ public interface FhirClientProvider * may be null * @return never null, {@link Optional#empty()} if no client is configured for the given * fhirServerId - * @see DsfClientProvider */ - Optional getClient(String fhirServerId); + Optional getById(String fhirServerId); } diff --git a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/OidcClientProvider.java b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/OidcClientProvider.java index 1965cf83c..8b0c0133f 100644 --- a/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/OidcClientProvider.java +++ b/dsf-bpe/dsf-bpe-process-api-v2/src/main/java/dev/dsf/bpe/v2/service/OidcClientProvider.java @@ -74,7 +74,7 @@ OidcClient getOidcClient(String baseUrl, String clientId, char[] clientSecret, S * @param config * not null * @return never null - * @see FhirClientConfigProvider#getClientConfig(String) + * @see ClientConfigProvider#getClientConfig(String) */ OidcClient getOidcClient(OidcAuthentication config); } diff --git a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/logging/AbstractPluginMdc.java b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/logging/AbstractPluginMdc.java index 191cca6d9..eb3672d5b 100644 --- a/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/logging/AbstractPluginMdc.java +++ b/dsf-bpe/dsf-bpe-process-api/src/main/java/dev/dsf/bpe/api/logging/AbstractPluginMdc.java @@ -73,7 +73,7 @@ private void putProcessMdc(DelegateExecution delegateExecution) ProcessValues processValues = getProcessValues(delegateExecution); if (processValues != null) { - // business-key added to mdc by camunda + // business-key added to mdc by workflow engine MDC.put(DSF_PROCESS, processValues.processUrl()); MDC.put(DSF_PROCESS_TASK_START, processValues.startTaskUrl()); MDC.put(DSF_PROCESS_REQUESTER_START, processValues.startRequesterIdentifierValue()); diff --git a/dsf-bpe/dsf-bpe-server-jetty/src/main/java/dev/dsf/bpe/config/BpeDbMigratorConfig.java b/dsf-bpe/dsf-bpe-server-jetty/src/main/java/dev/dsf/bpe/config/BpeDbMigratorConfig.java index bf0e557a1..bb64598c2 100644 --- a/dsf-bpe/dsf-bpe-server-jetty/src/main/java/dev/dsf/bpe/config/BpeDbMigratorConfig.java +++ b/dsf-bpe/dsf-bpe-server-jetty/src/main/java/dev/dsf/bpe/config/BpeDbMigratorConfig.java @@ -24,6 +24,7 @@ import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.ConfigurableEnvironment; +import dev.dsf.bpe.spring.config.PropertiesConfig; import dev.dsf.common.db.migration.DbMigrator; import dev.dsf.common.db.migration.DbMigratorConfig; import dev.dsf.common.docker.secrets.DockerSecretsPropertySourceFactory; @@ -37,9 +38,9 @@ public class BpeDbMigratorConfig implements DbMigratorConfig private static final String DB_SERVER_USERS_GROUP = "db.server_users_group"; private static final String DB_SERVER_USER = "db.server_user"; private static final String DB_SERVER_USER_PASSWORD = "db.server_user_password"; - private static final String DB_CAMUNDA_USERS_GROUP = "db.camunda_users_group"; - private static final String DB_CAMUNDA_USER = "db.camunda_user"; - private static final String DB_CAMUNDA_USER_PASSWORD = "db.camunda_user_password"; + private static final String DB_ENGINE_USERS_GROUP = "db.engine_users_group"; + private static final String DB_ENGINE_USER = "db.engine_user"; + private static final String DB_ENGINE_USER_PASSWORD = "db.engine_user_password"; @Documentation(required = true, description = "Address of the database used for the DSF BPE server", recommendation = "Change only if you don't use the provided docker-compose from the installation guide or made changes to the database settings/networking in the docker-compose", example = "jdbc:postgresql://db/bpe") @Value("${dev.dsf.bpe.db.url}") @@ -73,17 +74,17 @@ public class BpeDbMigratorConfig implements DbMigratorConfig @Value("${dev.dsf.bpe.db.liquibase.lockWaitTime:2}") private long dbLiquibaseLockWaitTime; - @Documentation(description = "Name of the user group to access the database from the DSF BPE server for camunda processes") - @Value("${dev.dsf.bpe.db.user.camunda.group:camunda_users}") - private String dbCamundaUsersGroup; + @Documentation(description = "Name of the user group to access the database from the DSF BPE server workflow engine") + @Value("${dev.dsf.bpe.db.user.engine.group:bpe_engine_users}") + private String dbEngineUsersGroup; - @Documentation(description = "Username to access the database from the DSF BPE server for camunda processes", recommendation = "Use a different user then in *DEV_DSF_BPE_DB_USER_USERNAME*") - @Value("${dev.dsf.bpe.db.user.camunda.username:camunda_server_user}") - private String dbCamundaUsername; + @Documentation(description = "Username to access the database from the DSF BPE server workflow engine", recommendation = "Use a different user then in *DEV_DSF_BPE_DB_USER_USERNAME*") + @Value("${dev.dsf.bpe.db.user.engine.username:bpe_server_engine_user}") + private String dbEngineUsername; - @Documentation(required = true, description = "Password to access the database from the DSF BPE server for camunda processes", recommendation = "Use docker secret file to configure using *${env_variable}_FILE*", example = "/run/secrets/db_user_camunda.password") - @Value("${dev.dsf.bpe.db.user.camunda.password}") - private char[] dbCamundaPassword; + @Documentation(required = true, description = "Password to access the database from the DSF BPE server workflow engine", recommendation = "Use docker secret file to configure using *${env_variable}_FILE*", example = "/run/secrets/db_user_engine.password") + @Value("${dev.dsf.bpe.db.user.engine.password}") + private char[] dbEnginePassword; @Bean // static in order to initialize before @Configuration classes public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer( @@ -91,6 +92,8 @@ public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderCon { new DockerSecretsPropertySourceFactory(environment).readDockerSecretsAndAddPropertiesToEnvironment(); + PropertiesConfig.injectEngineProperties(environment); + return new PropertySourcesPlaceholderConfigurer(); } @@ -122,8 +125,8 @@ public String getChangelogFile() public Map getChangeLogParameters() { return Map.of(DB_LIQUIBASE_USER, dbLiquibaseUsername, DB_SERVER_USERS_GROUP, dbUsersGroup, DB_SERVER_USER, - dbUsername, DB_SERVER_USER_PASSWORD, toString(dbPassword), DB_CAMUNDA_USERS_GROUP, dbCamundaUsersGroup, - DB_CAMUNDA_USER, dbCamundaUsername, DB_CAMUNDA_USER_PASSWORD, toString(dbCamundaPassword)); + dbUsername, DB_SERVER_USER_PASSWORD, toString(dbPassword), DB_ENGINE_USERS_GROUP, dbEngineUsersGroup, + DB_ENGINE_USER, dbEngineUsername, DB_ENGINE_USER_PASSWORD, toString(dbEnginePassword)); } private String toString(char[] password) diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/DelegateProvider.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProvider.java similarity index 98% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/DelegateProvider.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProvider.java index d90e5fba4..9ba15c6a7 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/DelegateProvider.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProvider.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import java.util.List; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/DelegateProviderImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProviderImpl.java similarity index 99% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/DelegateProviderImpl.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProviderImpl.java index f4f174380..223c9279b 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/DelegateProviderImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/DelegateProviderImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import java.util.HashMap; import java.util.List; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/FallbackSerializerFactory.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/FallbackSerializerFactory.java similarity index 96% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/FallbackSerializerFactory.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/FallbackSerializerFactory.java index 944ff72bf..762fd0439 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/FallbackSerializerFactory.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/FallbackSerializerFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import org.operaton.bpm.engine.impl.variable.serializer.VariableSerializerFactory; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/FallbackSerializerFactoryImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/FallbackSerializerFactoryImpl.java similarity index 99% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/FallbackSerializerFactoryImpl.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/FallbackSerializerFactoryImpl.java index 892458582..cc22ce66a 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/FallbackSerializerFactoryImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/FallbackSerializerFactoryImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import java.util.HashMap; import java.util.List; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionBpmnParse.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java similarity index 99% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionBpmnParse.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java index 2f0b4b672..1361f1c20 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionBpmnParse.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParse.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import java.util.ArrayList; import java.util.List; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionBpmnParseFactory.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParseFactory.java similarity index 97% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionBpmnParseFactory.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParseFactory.java index 80f2345bf..1145b3a84 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionBpmnParseFactory.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionBpmnParseFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import org.operaton.bpm.engine.impl.bpmn.parser.BpmnParse; import org.operaton.bpm.engine.impl.bpmn.parser.BpmnParser; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionClassDelegateActivityBehavior.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionClassDelegateActivityBehavior.java similarity index 99% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionClassDelegateActivityBehavior.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionClassDelegateActivityBehavior.java index 7b676c2bd..674de0935 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionClassDelegateActivityBehavior.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionClassDelegateActivityBehavior.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import java.util.List; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionClassDelegateExecutionListener.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionClassDelegateExecutionListener.java similarity index 98% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionClassDelegateExecutionListener.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionClassDelegateExecutionListener.java index d8c436c77..6aacb4bed 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionClassDelegateExecutionListener.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionClassDelegateExecutionListener.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import java.util.List; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionClassDelegateTaskListener.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionClassDelegateTaskListener.java similarity index 98% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionClassDelegateTaskListener.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionClassDelegateTaskListener.java index 1e36d14c3..7a149e752 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionClassDelegateTaskListener.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionClassDelegateTaskListener.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import java.util.List; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionSpringProcessEngineConfiguration.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionSpringProcessEngineConfiguration.java similarity index 97% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionSpringProcessEngineConfiguration.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionSpringProcessEngineConfiguration.java index 5ec3b8092..ccc2aa36c 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/MultiVersionSpringProcessEngineConfiguration.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/MultiVersionSpringProcessEngineConfiguration.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import org.operaton.bpm.engine.impl.telemetry.dto.TelemetryDataImpl; import org.operaton.bpm.engine.spring.SpringProcessEngineConfiguration; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/ProcessPluginConsumer.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/ProcessPluginConsumer.java similarity index 97% rename from dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/ProcessPluginConsumer.java rename to dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/ProcessPluginConsumer.java index 6608744ea..9506b5b8b 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/camunda/ProcessPluginConsumer.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/engine/ProcessPluginConsumer.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package dev.dsf.bpe.camunda; +package dev.dsf.bpe.engine; import java.util.List; import java.util.Map; diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/listener/DefaultBpmnParseListener.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/listener/DefaultBpmnParseListener.java index d04790f95..537d863ff 100755 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/listener/DefaultBpmnParseListener.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/listener/DefaultBpmnParseListener.java @@ -38,7 +38,7 @@ import dev.dsf.bpe.api.listener.ListenerFactory; import dev.dsf.bpe.api.plugin.ProcessIdAndVersion; import dev.dsf.bpe.api.plugin.ProcessPlugin; -import dev.dsf.bpe.camunda.ProcessPluginConsumer; +import dev.dsf.bpe.engine.ProcessPluginConsumer; public class DefaultBpmnParseListener implements BpmnParseListener, ProcessPluginConsumer { diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/plugin/ProcessPluginManagerImpl.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/plugin/ProcessPluginManagerImpl.java index 27ab81086..ebf7cb61c 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/plugin/ProcessPluginManagerImpl.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/plugin/ProcessPluginManagerImpl.java @@ -44,9 +44,9 @@ import dev.dsf.bpe.api.plugin.BpmnFileAndModel; import dev.dsf.bpe.api.plugin.ProcessIdAndVersion; import dev.dsf.bpe.api.plugin.ProcessPlugin; -import dev.dsf.bpe.camunda.ProcessPluginConsumer; import dev.dsf.bpe.client.dsf.BasicWebserviceClient; import dev.dsf.bpe.client.dsf.WebserviceClient; +import dev.dsf.bpe.engine.ProcessPluginConsumer; public class ProcessPluginManagerImpl implements ProcessPluginManager, InitializingBean { diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/OperatonConfig.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/OperatonConfig.java index 8604e24bc..929fe41a7 100755 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/OperatonConfig.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/OperatonConfig.java @@ -34,11 +34,11 @@ import org.springframework.transaction.PlatformTransactionManager; import dev.dsf.bpe.api.plugin.ProcessPluginFactory; -import dev.dsf.bpe.camunda.DelegateProvider; -import dev.dsf.bpe.camunda.DelegateProviderImpl; -import dev.dsf.bpe.camunda.FallbackSerializerFactory; -import dev.dsf.bpe.camunda.FallbackSerializerFactoryImpl; -import dev.dsf.bpe.camunda.MultiVersionSpringProcessEngineConfiguration; +import dev.dsf.bpe.engine.DelegateProvider; +import dev.dsf.bpe.engine.DelegateProviderImpl; +import dev.dsf.bpe.engine.FallbackSerializerFactory; +import dev.dsf.bpe.engine.FallbackSerializerFactoryImpl; +import dev.dsf.bpe.engine.MultiVersionSpringProcessEngineConfiguration; import dev.dsf.bpe.listener.DebugLoggingBpmnParseListener; import dev.dsf.bpe.listener.DefaultBpmnParseListener; @@ -57,23 +57,23 @@ public class OperatonConfig @Bean public PlatformTransactionManager transactionManager() { - return new DataSourceTransactionManager(camundaDataSource()); + return new DataSourceTransactionManager(engineDataSource()); } @Bean public TransactionAwareDataSourceProxy transactionAwareDataSource() { - return new TransactionAwareDataSourceProxy(camundaDataSource()); + return new TransactionAwareDataSourceProxy(engineDataSource()); } @Bean - public BasicDataSource camundaDataSource() + public BasicDataSource engineDataSource() { BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(Driver.class.getName()); dataSource.setUrl(propertiesConfig.getDbUrl()); - dataSource.setUsername(propertiesConfig.getDbCamundaUsername()); - dataSource.setPassword(toString(propertiesConfig.getDbCamundaPassword())); + dataSource.setUsername(propertiesConfig.getDbEngineUsername()); + dataSource.setPassword(toString(propertiesConfig.getDbEnginePassword())); dataSource.setTestOnBorrow(true); dataSource.setValidationQuery("SELECT 1"); diff --git a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java index f0503b9f5..68ebdb17b 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java +++ b/dsf-bpe/dsf-bpe-server/src/main/java/dev/dsf/bpe/spring/config/PropertiesConfig.java @@ -27,6 +27,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Properties; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -43,6 +44,7 @@ import org.springframework.context.annotation.Scope; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.core.env.PropertiesPropertySource; import de.hsheilbronn.mi.utils.crypto.cert.CertificateValidator; import de.hsheilbronn.mi.utils.crypto.io.PemReader; @@ -75,14 +77,14 @@ public class PropertiesConfig extends AbstractCertificateConfig implements Initi private char[] dbPassword; // documentation in dev.dsf.bpe.config.BpeDbMigratorConfig - @Value("${dev.dsf.bpe.db.user.camunda.username:camunda_server_user}") - private String dbCamundaUsername; + @Value("${dev.dsf.bpe.db.user.engine.username:bpe_server_engine_user}") + private String dbEngineUsername; // documentation in dev.dsf.bpe.config.BpeDbMigratorConfig - @Value("${dev.dsf.bpe.db.user.camunda.password}") - private char[] dbCamundaPassword; + @Value("${dev.dsf.bpe.db.user.engine.password}") + private char[] dbEnginePassword; - @Documentation(description = "UI theme parameter, adds a color indicator to the ui to distinguish `dev`, `test` and `prod` environments im configured; supported values: `dev`, `test` and `prod`") + @Documentation(description = "UI theme parameter, adds a color indicator to the ui to distinguish `dev`, `test` and `prod` environments if configured; supported values: `dev`, `test` and `prod`") @Value("${dev.dsf.bpe.server.ui.theme:}") private String uiTheme; @@ -150,7 +152,7 @@ public class PropertiesConfig extends AbstractCertificateConfig implements Initi @Value("${dev.dsf.bpe.fhir.client.connections.config.default.test.connection.on.startup:false}") private boolean fhirClientConnectionsConfigDefaultTestConnectionOnStartup; - @Documentation(description = "FHIR server connections YAML: Default value for properties `enable-debug-logging` and `oidc-auth.enable-debug-logging`", recommendation = "To enable debug logging of requests and reponses to configured FHIR servers by default set to `true`") + @Documentation(description = "FHIR server connections YAML: Default value for properties `enable-debug-logging` and `oidc-auth.enable-debug-logging`", recommendation = "To enable debug logging of requests and responses to configured FHIR servers by default set to `true`") @Value("${dev.dsf.bpe.fhir.client.connections.config.default.enable.debug.logging:false}") private boolean fhirClientConnectionsConfigDefaultEnableDebugLogging; @@ -174,7 +176,7 @@ public class PropertiesConfig extends AbstractCertificateConfig implements Initi @Value("${dev.dsf.bpe.fhir.client.connections.config.default.oidc.discovery.path:/.well-known/openid-configuration}") private String fhirClientConnectionsConfigDefaultOidcDiscoveryPath; - @Documentation(description = "Set `false` to disable caching of OIDC dicovery and jwks resources as well as access tokens in the 'Client Credentials Grant' client; access tokens are evicted 10 seconds before they expire") + @Documentation(description = "Set `false` to disable caching of OIDC discovery and jwks resources as well as access tokens in the 'Client Credentials Grant' client; access tokens are evicted 10 seconds before they expire") @Value("${dev.dsf.bpe.fhir.client.connections.config.oidc.cache:true}") private boolean fhirClientConnectionsConfigOidcClientCacheEnabled; @@ -186,7 +188,7 @@ public class PropertiesConfig extends AbstractCertificateConfig implements Initi @Value("${dev.dsf.bpe.fhir.client.connections.config.oidc.cache.timeout.jwks.resource:PT1H}") private String fhirClientConnectionsConfigOidcClientCacheJwksResourceTimeout; - @Documentation(description = "OIDC 'Client Credentials Grant' client cache timeout of access tokens before they expire, duration is subtracted from the expires at value of the acess token") + @Documentation(description = "OIDC 'Client Credentials Grant' client cache timeout of access tokens before they expire, duration is subtracted from the expires at value of the access token") @Value("${dev.dsf.bpe.fhir.client.connections.config.oidc.cache.timeout.access.token:PT10S}") private String fhirClientConnectionsConfigOidcClientCacheAccessTokenBeforeExpirationTimeout; @@ -222,7 +224,7 @@ public class PropertiesConfig extends AbstractCertificateConfig implements Initi @Value("${dev.dsf.bpe.process.api.directory:api}") private String apiClassPathBaseDirectory; - @Documentation(description = "Map with files containing qualified classs names allowed to be loaded by plugins for api versions; map key must match " + @Documentation(description = "Map with files containing qualified class names allowed to be loaded by plugins for api versions; map key must match " + API_VERSION_PATTERN_STRING, recommendation = "Change only during development", example = "{v1: 'some/example.file', v2: 'other.file'}") @Value("#{${dev.dsf.bpe.process.api.allowed.bpe.classes:{:}}}") private Map apiAllowedBpeClasses; @@ -301,11 +303,11 @@ public class PropertiesConfig extends AbstractCertificateConfig implements Initi @Value("${dev.dsf.bpe.mail.port:0}") private int mailServerPort; - @Documentation(description = "SMTP server authentication username", recommendation = "Configure if the SMTP server reqiures username/password authentication; enable SMTP over TLS via *DEV_DSF_BPE_MAIL_USESMTPS*") + @Documentation(description = "SMTP server authentication username", recommendation = "Configure if the SMTP server requires username/password authentication; enable SMTP over TLS via *DEV_DSF_BPE_MAIL_USESMTPS*") @Value("${dev.dsf.bpe.mail.username:#{null}}") private String mailServerUsername; - @Documentation(description = "SMTP server authentication password", recommendation = "Configure if the SMTP server reqiures username/password authentication; use docker secret file to configure using *${env_variable}_FILE*; enable SMTP over TLS via *DEV_DSF_BPE_MAIL_USESMTPS*") + @Documentation(description = "SMTP server authentication password", recommendation = "Configure if the SMTP server requires username/password authentication; use docker secret file to configure using *${env_variable}_FILE*; enable SMTP over TLS via *DEV_DSF_BPE_MAIL_USESMTPS*") @Value("${dev.dsf.bpe.mail.password:#{null}}") private char[] mailServerPassword; @@ -407,9 +409,45 @@ public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderCon { new DockerSecretsPropertySourceFactory(environment).readDockerSecretsAndAddPropertiesToEnvironment(); + injectEngineProperties(environment); + return new PropertySourcesPlaceholderConfigurer(); } + public static void injectEngineProperties(ConfigurableEnvironment environment) + { + Properties properties = new Properties(); + + injectEngineProperty(environment, "dev.dsf.bpe.db.user.camunda.group", "dev.dsf.bpe.db.user.engine.group", + properties); + injectEngineProperty(environment, "dev.dsf.bpe.db.user.camunda.username", "dev.dsf.bpe.db.user.engine.username", + properties); + injectEngineProperty(environment, "dev.dsf.bpe.db.user.camunda.password", "dev.dsf.bpe.db.user.engine.password", + properties); + + if (!properties.isEmpty()) + environment.getPropertySources().addFirst(new PropertiesPropertySource("engine-properties", properties)); + } + + private static void injectEngineProperty(ConfigurableEnvironment environment, String oldPropertyName, + String newPropertyName, Properties properties) + { + String oldPropertyValue = environment.getProperty(oldPropertyName); + String newPropertyValue = environment.getProperty(newPropertyName); + + if (oldPropertyValue != null && newPropertyValue != null) + { + logger.error("Property '{}' and old property '{}' defined", newPropertyName, oldPropertyName); + throw new RuntimeException( + "Property '" + newPropertyName + "' and old property '" + oldPropertyName + "' defined"); + } + else if (oldPropertyValue != null && newPropertyValue == null) + { + logger.warn("Setting property '{}' with value from old property '{}'", newPropertyName, oldPropertyName); + properties.put(newPropertyName, oldPropertyValue); + } + } + @Override public void afterPropertiesSet() throws Exception { @@ -475,14 +513,14 @@ public char[] getDbPassword() return dbPassword; } - public String getDbCamundaUsername() + public String getDbEngineUsername() { - return dbCamundaUsername; + return dbEngineUsername; } - public char[] getDbCamundaPassword() + public char[] getDbEnginePassword() { - return dbCamundaPassword; + return dbEnginePassword; } public Theme getUiTheme() diff --git a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.camunda_engine.changelog-1.0.0.xml b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.camunda_engine.changelog-1.0.0.xml index fb3fa7d3d..bf71726b3 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.camunda_engine.changelog-1.0.0.xml +++ b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.camunda_engine.changelog-1.0.0.xml @@ -24,91 +24,94 @@ logicalFilePath="db/db.camunda_engine.changelog-1.0.0.xml"> + 9:bf9fe960d3004f50a7248a45e07d8fb8 + + GRANT ALL ON TABLE ACT_GE_SCHEMA_LOG TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_GE_SCHEMA_LOG TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_GE_SCHEMA_LOG TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_GE_PROPERTY TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_GE_PROPERTY TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_GE_PROPERTY TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_GE_BYTEARRAY TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_GE_BYTEARRAY TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_GE_BYTEARRAY TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RE_DEPLOYMENT TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_DEPLOYMENT TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_DEPLOYMENT TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_EXECUTION TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_EXECUTION TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_EXECUTION TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_JOB TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_JOB TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_JOB TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_JOBDEF TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_JOBDEF TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_JOBDEF TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RE_PROCDEF TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_PROCDEF TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_PROCDEF TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_TASK TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_TASK TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_TASK TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_IDENTITYLINK TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_IDENTITYLINK TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_IDENTITYLINK TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_VARIABLE TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_VARIABLE TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_VARIABLE TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_EVENT_SUBSCR TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_EVENT_SUBSCR TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_EVENT_SUBSCR TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_INCIDENT TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_INCIDENT TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_INCIDENT TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_AUTHORIZATION TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_AUTHORIZATION TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_AUTHORIZATION TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_FILTER TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_FILTER TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_FILTER TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_METER_LOG TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_METER_LOG TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_METER_LOG TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_EXT_TASK TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_EXT_TASK TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_EXT_TASK TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_BATCH TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_BATCH TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_BATCH TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RE_CASE_DEF TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_CASE_DEF TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_CASE_DEF TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_CASE_EXECUTION TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_CASE_EXECUTION TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_CASE_EXECUTION TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RU_CASE_SENTRY_PART TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_CASE_SENTRY_PART TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RU_CASE_SENTRY_PART TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RE_DECISION_DEF TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_DECISION_DEF TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_DECISION_DEF TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_RE_DECISION_REQ_DEF TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_DECISION_REQ_DEF TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_RE_DECISION_REQ_DEF TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_PROCINST TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_PROCINST TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_PROCINST TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_ACTINST TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_ACTINST TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_ACTINST TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_TASKINST TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_TASKINST TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_TASKINST TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_VARINST TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_VARINST TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_VARINST TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_DETAIL TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_DETAIL TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_DETAIL TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_IDENTITYLINK TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_IDENTITYLINK TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_IDENTITYLINK TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_COMMENT TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_COMMENT TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_COMMENT TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_ATTACHMENT TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_ATTACHMENT TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_ATTACHMENT TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_OP_LOG TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_OP_LOG TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_OP_LOG TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_INCIDENT TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_INCIDENT TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_INCIDENT TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_JOB_LOG TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_JOB_LOG TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_JOB_LOG TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_BATCH TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_BATCH TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_BATCH TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_EXT_TASK_LOG TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_EXT_TASK_LOG TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_EXT_TASK_LOG TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_CASEINST TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_CASEINST TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_CASEINST TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_CASEACTINST TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_CASEACTINST TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_CASEACTINST TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_DECINST TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_DECINST TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_DECINST TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_DEC_IN TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_DEC_IN TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_DEC_IN TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_HI_DEC_OUT TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_DEC_OUT TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_HI_DEC_OUT TO ${db.engine_users_group}; diff --git a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.camunda_identity.changelog-1.0.0.xml b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.camunda_identity.changelog-1.0.0.xml index 080092bae..b171e6b3f 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.camunda_identity.changelog-1.0.0.xml +++ b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.camunda_identity.changelog-1.0.0.xml @@ -24,21 +24,24 @@ logicalFilePath="db/db.camunda_identity.changelog-1.0.0.xml"> + 9:09445d67a79866964af3de295fa58b94 + + GRANT ALL ON TABLE ACT_ID_GROUP TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_GROUP TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_GROUP TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_ID_MEMBERSHIP TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_MEMBERSHIP TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_MEMBERSHIP TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_ID_USER TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_USER TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_USER TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_ID_INFO TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_INFO TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_INFO TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_ID_TENANT TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_TENANT TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_TENANT TO ${db.engine_users_group}; GRANT ALL ON TABLE ACT_ID_TENANT_MEMBER TO ${db.liquibase_user}; - GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_TENANT_MEMBER TO ${db.camunda_users_group}; + GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE ACT_ID_TENANT_MEMBER TO ${db.engine_users_group}; diff --git a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.changelog.xml b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.changelog.xml index bbaa21e40..c7b51cb1b 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.changelog.xml +++ b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.changelog.xml @@ -21,6 +21,7 @@ xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd"> + diff --git a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.create-db-users.changelog-1.0.0.xml b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.create-db-users.changelog-1.0.0.xml index 78f6d3b86..18b73551b 100644 --- a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.create-db-users.changelog-1.0.0.xml +++ b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.create-db-users.changelog-1.0.0.xml @@ -22,22 +22,22 @@ http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd" logicalFilePath="db/db.create-db-users.changelog-1.0.0.xml"> - + SELECT COUNT(*) FROM pg_roles WHERE rolname='${db.server_user}' SELECT COUNT(*) FROM pg_roles WHERE rolname='${db.server_users_group}' - SELECT COUNT(*) FROM pg_roles WHERE rolname='${db.camunda_user}' - SELECT COUNT(*) FROM pg_roles WHERE rolname='${db.camunda_users_group}' + SELECT COUNT(*) FROM pg_roles WHERE rolname='${db.engine_user}' + SELECT COUNT(*) FROM pg_roles WHERE rolname='${db.engine_users_group}' CREATE ROLE ${db.server_users_group} NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION; CREATE ROLE ${db.server_user} LOGIN PASSWORD '${db.server_user_password}' NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION; GRANT ${db.server_users_group} TO ${db.server_user}; - CREATE ROLE ${db.camunda_users_group} NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION; - CREATE ROLE ${db.camunda_user} LOGIN PASSWORD '${db.camunda_user_password}' NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION; - GRANT ${db.camunda_users_group} TO ${db.camunda_user}; + CREATE ROLE ${db.engine_users_group} NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION; + CREATE ROLE ${db.engine_user} LOGIN PASSWORD '${db.engine_user_password}' NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION; + GRANT ${db.engine_users_group} TO ${db.engine_user}; \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.rename-db-users.changelog-2.0.0.xml b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.rename-db-users.changelog-2.0.0.xml new file mode 100644 index 000000000..4ef85e52f --- /dev/null +++ b/dsf-bpe/dsf-bpe-server/src/main/resources/bpe/db/db.rename-db-users.changelog-2.0.0.xml @@ -0,0 +1,44 @@ + + + + + + + + SELECT COUNT(*) FROM pg_roles WHERE rolname='camunda_users' + + + + ALTER ROLE camunda_users RENAME TO ${db.engine_users_group}; + + + + + + SELECT COUNT(*) FROM pg_roles WHERE rolname='camunda_server_user' + + + + ALTER ROLE camunda_server_user RENAME TO ${db.engine_user}; + + + \ No newline at end of file diff --git a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/dao/AbstractDbTest.java b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/dao/AbstractDbTest.java index 8a95fc2b5..1924e1263 100644 --- a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/dao/AbstractDbTest.java +++ b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/dao/AbstractDbTest.java @@ -41,9 +41,9 @@ public abstract class AbstractDbTest protected static final String BPE_DATABASE_USER = "server_user"; protected static final String BPE_DATABASE_USER_PASSWORD = "server_user_password"; - protected static final String BPE_DATABASE_CAMUNDA_USERS_GROUP = "camunda_users_group"; - protected static final String BPE_DATABASE_CAMUNDA_USER = "camunda_user"; - protected static final String BPE_DATABASE_CAMUNDA_USER_PASSWORD = "camunda_user_password"; + protected static final String BPE_DATABASE_ENGINE_USERS_GROUP = "engine_users_group"; + protected static final String BPE_DATABASE_ENGINE_USER = "engine_user"; + protected static final String BPE_DATABASE_ENGINE_USER_PASSWORD = "engine_user_password"; protected static final String FHIR_CHANGE_LOG_FILE = "fhir/db/db.changelog.xml"; @@ -59,9 +59,9 @@ public abstract class AbstractDbTest protected static final Map BPE_CHANGE_LOG_PARAMETERS = Map.of("db.liquibase_user", ROOT_USER, "db.server_users_group", BPE_DATABASE_USERS_GROUP, "db.server_user", BPE_DATABASE_USER, - "db.server_user_password", BPE_DATABASE_USER_PASSWORD, "db.camunda_users_group", - BPE_DATABASE_CAMUNDA_USERS_GROUP, "db.camunda_user", BPE_DATABASE_CAMUNDA_USER, "db.camunda_user_password", - BPE_DATABASE_CAMUNDA_USER_PASSWORD); + "db.server_user_password", BPE_DATABASE_USER_PASSWORD, "db.engine_users_group", + BPE_DATABASE_ENGINE_USERS_GROUP, "db.engine_user", BPE_DATABASE_ENGINE_USER, "db.engine_user_password", + BPE_DATABASE_ENGINE_USER_PASSWORD); protected static final Map FHIR_CHANGE_LOG_PARAMETERS = Map.of("db.liquibase_user", ROOT_USER, "db.server_users_group", FHIR_DATABASE_USERS_GROUP, "db.server_user", FHIR_DATABASE_USER, @@ -89,8 +89,8 @@ public static DataSource createBpeCamundaDataSource(String host, int port, Strin BasicDataSource dataSource = new BasicDataSource(); dataSource.setDriverClassName(Driver.class.getName()); dataSource.setUrl("jdbc:postgresql://" + host + ":" + port + "/" + databaseName); - dataSource.setUsername(BPE_DATABASE_CAMUNDA_USER); - dataSource.setPassword(BPE_DATABASE_CAMUNDA_USER_PASSWORD); + dataSource.setUsername(BPE_DATABASE_ENGINE_USER); + dataSource.setPassword(BPE_DATABASE_ENGINE_USER_PASSWORD); dataSource.setDefaultReadOnly(true); dataSource.setTestOnBorrow(true); diff --git a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java index 59a76d214..a00686f1c 100644 --- a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java +++ b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/AbstractIntegrationTest.java @@ -133,6 +133,8 @@ public abstract class AbstractIntegrationTest extends AbstractDbTest private static final ReferenceCleaner referenceCleaner = new ReferenceCleanerImpl(new ReferenceExtractorImpl()); + protected static TestFhirDataServer testFhirDataServer; + private static JettyServer fhirServer; private static FhirWebserviceClient webserviceClient; private static JettyServer bpeServer; @@ -157,8 +159,6 @@ public static void beforeClass() throws Exception logger.info("Starting FHIR Server ..."); fhirServer = startFhirServer(fhirStatusConnectorChannel, fhirApiConnectorChannel, fhirBaseUrl); - // --- bpe --- - // allowed bpe classes override to enable access to classes from dsf-bpe-test-plugin module for v1 test plugins List allowedBpeClassesV1 = readListFile( Paths.get("src/main/resources/bpe/api/v1/allowed-bpe-classes.list")); @@ -186,6 +186,8 @@ public static void beforeClass() throws Exception Files.createDirectories(EMPTY_PROCESS_DIRECTORY); + testFhirDataServer = new TestFhirDataServer(certificates.getFhirServerCertificate()); + logger.info("Starting BPE Server ..."); bpeServer = startBpeServer(bpeStatusConnectorChannel, bpeApiConnectorChannel, bpeBaseUrl, fhirBaseUrl); @@ -366,8 +368,8 @@ private static JettyServer startBpeServer(ServerSocketChannel statusConnectorCha initParameters.put("dev.dsf.bpe.db.user.group", BPE_DATABASE_USERS_GROUP); initParameters.put("dev.dsf.bpe.db.user.username", BPE_DATABASE_USER); initParameters.put("dev.dsf.bpe.db.user.password", BPE_DATABASE_USER_PASSWORD); - initParameters.put("dev.dsf.bpe.db.user.camunda.username", BPE_DATABASE_CAMUNDA_USER); - initParameters.put("dev.dsf.bpe.db.user.camunda.password", BPE_DATABASE_CAMUNDA_USER_PASSWORD); + initParameters.put("dev.dsf.bpe.db.user.engine.username", BPE_DATABASE_ENGINE_USER); + initParameters.put("dev.dsf.bpe.db.user.engine.password", BPE_DATABASE_ENGINE_USER_PASSWORD); initParameters.put("dev.dsf.bpe.fhir.client.certificate", certificates.getClientCertificateFile().toString()); initParameters.put("dev.dsf.bpe.fhir.client.certificate.private.key", @@ -398,6 +400,8 @@ private static JettyServer startBpeServer(ServerSocketChannel statusConnectorCha initParameters.put("dev.dsf.proxy.noProxy", "localhost, noproxy:443"); final String fhirConnectionsYaml = """ + test-fhir-data-server: + base-url: '#[testFhirDataServerBaseUrl]' dsf-fhir-server: base-url: '#[fhirBaseUrl]' test-connection-on-startup: yes @@ -424,7 +428,10 @@ private static JettyServer startBpeServer(ServerSocketChannel statusConnectorCha private-key-file: '#[uac.client.key]' certificate-file: '#[uac.client.crt]' password: '#[password]' - """.replaceAll(Pattern.quote("#[fhirBaseUrl]"), Matcher.quoteReplacement(fhirBaseUrl)) + """ + .replaceAll(Pattern.quote("#[testFhirDataServerBaseUrl]"), + Matcher.quoteReplacement("https://localhost:" + testFhirDataServer.getAddress().getPort())) + .replaceAll(Pattern.quote("#[fhirBaseUrl]"), Matcher.quoteReplacement(fhirBaseUrl)) .replaceAll(Pattern.quote("#[client.key]"), Matcher.quoteReplacement(certificates.getClientCertificatePrivateKeyFile().toString())) .replaceAll(Pattern.quote("#[client.crt]"), 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 a842903c4..74751a493 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 @@ -257,14 +257,23 @@ public void startEnvironmentVariableTest() throws Exception @Test public void startDsfClientTest() throws Exception { - Binary binary = new Binary(); - new ReadAccessHelperImpl().addLocal(binary); - binary.setData(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); - binary.setContentType(MediaType.APPLICATION_OCTET_STREAM); - Binary created = getWebserviceClient().create(binary); - assertNotNull(created); - - executePluginTest(createTestTask("DsfClientTest")); + testFhirDataServer.start(); + + try + { + Binary binary = new Binary(); + new ReadAccessHelperImpl().addLocal(binary); + binary.setData(new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }); + binary.setContentType(MediaType.APPLICATION_OCTET_STREAM); + Binary created = getWebserviceClient().create(binary); + assertNotNull(created); + + executePluginTest(createTestTask("DsfClientTest")); + } + finally + { + testFhirDataServer.stop(); + } } @Test diff --git a/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/TestFhirDataServer.java b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/TestFhirDataServer.java new file mode 100644 index 000000000..136c67c4b --- /dev/null +++ b/dsf-bpe/dsf-bpe-server/src/test/java/dev/dsf/bpe/integration/TestFhirDataServer.java @@ -0,0 +1,183 @@ +/* + * Copyright 2018-2025 Heilbronn University of Applied Sciences + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.dsf.bpe.integration; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.DiagnosticReport; +import org.hl7.fhir.r4.model.DiagnosticReport.DiagnosticReportStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.net.httpserver.HttpsConfigurator; +import com.sun.net.httpserver.HttpsServer; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.Constants; +import de.hsheilbronn.mi.utils.crypto.context.SSLContextFactory; +import de.hsheilbronn.mi.utils.crypto.keystore.KeyStoreCreator; +import dev.dsf.bpe.integration.X509Certificates.CertificateAndPrivateKey; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response.Status; + +public class TestFhirDataServer +{ + private static final Logger logger = LoggerFactory.getLogger(TestFhirDataServer.class); + + private final HttpsServer server; + + public TestFhirDataServer(CertificateAndPrivateKey serverCertificate) + { + FhirContext context = FhirContext.forR4(); + + try + { + char[] keyStorePassword = UUID.randomUUID().toString().toCharArray(); + KeyStore keyStore = KeyStoreCreator.jksForPrivateKeyAndCertificateChain(serverCertificate.privateKey(), + keyStorePassword, serverCertificate.certificate(), serverCertificate.caCertificate()); + + server = HttpsServer.create(new InetSocketAddress("localhost", 0), 0); + server.setHttpsConfigurator( + new HttpsConfigurator(SSLContextFactory.createSSLContext(null, keyStore, keyStorePassword, "TLS"))); + + AtomicReference counter = new AtomicReference<>(0); + + server.createContext("/Patient", exchange -> + { + logger.info("GET /Patient"); + + counter.set(0); + + exchange.getResponseHeaders().set(HttpHeaders.LOCATION, + "https://localhost:" + server.getAddress().getPort() + "/async/Patient"); + exchange.sendResponseHeaders(Status.ACCEPTED.getStatusCode(), 0); + exchange.close(); + }); + + server.createContext("/async/Patient", exchange -> + { + logger.info("GET /async/Patient"); + + Integer c = counter.updateAndGet(i -> ++i); + if (c <= 2) + { + exchange.sendResponseHeaders(Status.ACCEPTED.getStatusCode(), 0); + exchange.close(); + } + + Bundle response = new Bundle().setType(BundleType.BATCHRESPONSE); + response.addEntry().setResource(new Bundle().setType(BundleType.SEARCHSET).setTotal(0)).getResponse() + .setStatus("200 OK").setLocation("Patient"); + String jsonResponse = context.newJsonParser().encodeResourceToString(response); + + exchange.getResponseHeaders().set(HttpHeaders.CONTENT_TYPE, Constants.CT_FHIR_JSON_NEW); + exchange.sendResponseHeaders(Status.OK.getStatusCode(), jsonResponse.getBytes().length); + + try (OutputStream os = exchange.getResponseBody()) + { + os.write(jsonResponse.getBytes()); + } + + exchange.close(); + }); + + server.createContext("/DiagnosticReport/$test", exchange -> + { + logger.info("POST /DiagnosticReport/$test"); + + counter.set(0); + + exchange.getResponseHeaders().set(HttpHeaders.LOCATION, + "https://localhost:" + server.getAddress().getPort() + "/async/DiagnosticReport"); + exchange.getResponseHeaders().set(HttpHeaders.RETRY_AFTER, + DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss O").withZone(ZoneOffset.UTC) + .format(ZonedDateTime.now().plusSeconds(2))); + exchange.sendResponseHeaders(Status.ACCEPTED.getStatusCode(), 0); + exchange.close(); + }); + + server.createContext("/async/DiagnosticReport", exchange -> + { + logger.info("GET /async/DiagnosticReport"); + + Integer c = counter.updateAndGet(i -> ++i); + if (c <= 2) + { + exchange.getResponseHeaders().set(HttpHeaders.RETRY_AFTER, "1"); + exchange.sendResponseHeaders(Status.ACCEPTED.getStatusCode(), 0); + exchange.close(); + } + + Bundle response = new Bundle().setType(BundleType.BATCHRESPONSE); + response.addEntry().setResource(new DiagnosticReport().setStatus(DiagnosticReportStatus.PARTIAL)) + .getResponse().setStatus("200 OK"); + String jsonResponse = context.newJsonParser().encodeResourceToString(response); + + exchange.getResponseHeaders().set(HttpHeaders.CONTENT_TYPE, Constants.CT_FHIR_JSON_NEW); + exchange.sendResponseHeaders(Status.OK.getStatusCode(), jsonResponse.getBytes().length); + + try (OutputStream os = exchange.getResponseBody()) + { + os.write(jsonResponse.getBytes()); + } + + exchange.close(); + }); + + server.createContext("/Observation", exchange -> + { + logger.info("GET /Observation"); + + exchange.sendResponseHeaders(Status.GATEWAY_TIMEOUT.getStatusCode(), 0); + exchange.close(); + }); + } + catch (IOException | UnrecoverableKeyException | KeyManagementException | KeyStoreException + | NoSuchAlgorithmException e) + { + throw new RuntimeException(e); + } + } + + public InetSocketAddress getAddress() + { + return server.getAddress(); + } + + public void stop() + { + server.stop(0); + } + + public void start() + { + server.start(); + } +} diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/DsfClientTest.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/DsfClientTest.java index 4ba1452b8..afd92168e 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/DsfClientTest.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/DsfClientTest.java @@ -15,26 +15,36 @@ */ 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 static dev.dsf.bpe.test.PluginTestExecutor.expectTrue; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.DiagnosticReport; import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Observation; +import org.hl7.fhir.r4.model.Patient; 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.client.dsf.BinaryInputStream; +import dev.dsf.bpe.v2.client.dsf.DelayStrategy; import dev.dsf.bpe.v2.client.dsf.DsfClient; import dev.dsf.bpe.v2.error.ErrorBoundaryEvent; import dev.dsf.bpe.v2.variables.Variables; +import jakarta.ws.rs.WebApplicationException; import jakarta.ws.rs.core.MediaType; public class DsfClientTest extends AbstractTest implements ServiceTask @@ -42,7 +52,7 @@ public class DsfClientTest extends AbstractTest implements ServiceTask @Override public void execute(ProcessPluginApi api, Variables variables) throws ErrorBoundaryEvent, Exception { - DsfClient localDsfClient = api.getDsfClientProvider().getLocalDsfClient(); + DsfClient localDsfClient = api.getDsfClientProvider().getLocal(); Bundle search = localDsfClient.search(Binary.class, Map.of("_count", List.of("1"))); IdType testBinaryId = search.getEntry().stream().filter(BundleEntryComponent::hasResource) @@ -141,4 +151,88 @@ public void downloadRangeVerion(DsfClient localDsfClient, IdType testBinaryId) t expectSame(1, allBytes[1]); } } + + @PluginTest + public void searchAsyncResourceMap(ProcessPluginApi api) throws Exception + { + Optional client = api.getDsfClientProvider().getById("test-fhir-data-server"); + expectTrue(client.isPresent()); + + CompletableFuture fBundle = client.get().searchAsync(Patient.class, Map.of()); + expectNotNull(fBundle); + + Bundle bundle = fBundle.get(); + expectNotNull(bundle); + expectSame(0, bundle.getTotal()); + } + + @PluginTest + public void searchAsyncUrl(ProcessPluginApi api) throws Exception + { + Optional client = api.getDsfClientProvider().getById("test-fhir-data-server"); + expectTrue(client.isPresent()); + + CompletableFuture fBundle = client.get().searchAsync(client.get().getBaseUrl() + "/Patient"); + expectNotNull(fBundle); + + Bundle bundle = fBundle.get(); + expectNotNull(bundle); + expectSame(0, bundle.getTotal()); + } + + @PluginTest + public void searchAsyncResourceMapStrict(ProcessPluginApi api) throws Exception + { + Optional client = api.getDsfClientProvider().getById("test-fhir-data-server"); + expectTrue(client.isPresent()); + + CompletableFuture fBundle = client.get().searchAsyncWithStrictHandling(Patient.class, Map.of()); + expectNotNull(fBundle); + + Bundle bundle = fBundle.get(); + expectNotNull(bundle); + expectSame(0, bundle.getTotal()); + } + + @PluginTest + public void searchAsyncUrlStrict(ProcessPluginApi api) throws Exception + { + Optional client = api.getDsfClientProvider().getById("test-fhir-data-server"); + expectTrue(client.isPresent()); + + CompletableFuture fBundle = client.get() + .searchAsyncWithStrictHandling(client.get().getBaseUrl() + "/Patient"); + expectNotNull(fBundle); + + Bundle bundle = fBundle.get(); + expectNotNull(bundle); + expectSame(0, bundle.getTotal()); + } + + @PluginTest + public void readWithRetry(ProcessPluginApi api) throws Exception + { + Optional client = api.getDsfClientProvider().getById("test-fhir-data-server"); + expectTrue(client.isPresent()); + + expectException(WebApplicationException.class, () -> + { + client.get().withRetry(5, DelayStrategy.TRUNCATED_EXPONENTIAL_BACKOFF).read(Observation.class, + UUID.randomUUID().toString()); + }); + } + + @PluginTest + public void operationAsync(ProcessPluginApi api) throws Exception + { + Optional client = api.getDsfClientProvider().getById("test-fhir-data-server"); + expectTrue(client.isPresent()); + + CompletableFuture operationAsync = client.get().operationAsync(DiagnosticReport.class, "test", + null, DiagnosticReport.class); + expectNotNull(operationAsync); + + DiagnosticReport r = operationAsync.get(); + expectNotNull(r); + } } diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/FhirClientConfigProviderTest.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/FhirClientConfigProviderTest.java index 9326d02b8..aa0d37d33 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/FhirClientConfigProviderTest.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/FhirClientConfigProviderTest.java @@ -31,7 +31,7 @@ import dev.dsf.bpe.v2.ProcessPluginApi; import dev.dsf.bpe.v2.activity.ServiceTask; import dev.dsf.bpe.v2.error.ErrorBoundaryEvent; -import dev.dsf.bpe.v2.service.FhirClientConfigProvider; +import dev.dsf.bpe.v2.service.ClientConfigProvider; import dev.dsf.bpe.v2.variables.Variables; public class FhirClientConfigProviderTest extends AbstractTest implements ServiceTask @@ -49,7 +49,7 @@ public void getFhirClientProviderNotNull(ProcessPluginApi api) throws Exception } @PluginTest - public void getClientConfigDsfFhirServer(FhirClientConfigProvider configProvider) throws Exception + public void getClientConfigDsfFhirServer(ClientConfigProvider configProvider) throws Exception { expectNotNull(configProvider.getClientConfig("dsf-fhir-server")); expectTrue(configProvider.getClientConfig("dsf-fhir-server").isPresent()); @@ -83,8 +83,7 @@ public void getClientConfigDsfFhirServer(FhirClientConfigProvider configProvider } @PluginTest - public void getClientConfigDsfFhirServerViaEndpointIdentifier(FhirClientConfigProvider configProvider) - throws Exception + public void getClientConfigDsfFhirServerViaEndpointIdentifier(ClientConfigProvider configProvider) throws Exception { expectNotNull(configProvider.getClientConfig("#Test_Endpoint")); expectTrue(configProvider.getClientConfig("#Test_Endpoint").isPresent()); @@ -118,7 +117,7 @@ public void getClientConfigDsfFhirServerViaEndpointIdentifier(FhirClientConfigPr } @PluginTest - public void getClientConfigDsfFhirServerViaLocal(FhirClientConfigProvider configProvider) throws Exception + public void getClientConfigDsfFhirServerViaLocal(ClientConfigProvider configProvider) throws Exception { expectNotNull(configProvider.getClientConfig("#local")); expectTrue(configProvider.getClientConfig("#local").isPresent()); @@ -152,7 +151,7 @@ public void getClientConfigDsfFhirServerViaLocal(FhirClientConfigProvider config } @PluginTest - public void getClientConfigViaProxy(FhirClientConfigProvider configProvider) throws Exception + public void getClientConfigViaProxy(ClientConfigProvider configProvider) throws Exception { expectNotNull(configProvider.getClientConfig("via-proxy")); expectTrue(configProvider.getClientConfig("via-proxy").isPresent()); @@ -187,14 +186,14 @@ public void getClientConfigViaProxy(FhirClientConfigProvider configProvider) thr } @PluginTest - public void getClientConfigWithNotConfiguredServerId(FhirClientConfigProvider configProvider) throws Exception + public void getClientConfigWithNotConfiguredServerId(ClientConfigProvider configProvider) throws Exception { expectNotNull(configProvider.getClientConfig("not-configured")); expectFalse(configProvider.getClientConfig("not-configured").isPresent()); } @PluginTest - public void getDefaultTrustStore(FhirClientConfigProvider configProvider) throws Exception + public void getDefaultTrustStore(ClientConfigProvider configProvider) throws Exception { KeyStore trustStore = configProvider.createDefaultTrustStore(); @@ -205,7 +204,7 @@ public void getDefaultTrustStore(FhirClientConfigProvider configProvider) throws } @PluginTest - public void getDefaultSslContext(FhirClientConfigProvider configProvider) throws Exception + public void getDefaultSslContext(ClientConfigProvider configProvider) throws Exception { expectNotNull(configProvider.createDefaultSslContext()); } diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/FhirClientProviderTest.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/FhirClientProviderTest.java index df8ea2c6c..244d769ec 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/FhirClientProviderTest.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/FhirClientProviderTest.java @@ -47,21 +47,21 @@ public void getFhirClientProviderNotNull(ProcessPluginApi api) throws Exception @PluginTest public void getClientWithConfiguredServerId(FhirClientProvider clientProvider) throws Exception { - expectNotNull(clientProvider.getClient("dsf-fhir-server")); - expectTrue(clientProvider.getClient("dsf-fhir-server").isPresent()); + expectNotNull(clientProvider.getById("dsf-fhir-server")); + expectTrue(clientProvider.getById("dsf-fhir-server").isPresent()); } @PluginTest public void getClientWithNotConfiguredServerId(FhirClientProvider clientProvider) throws Exception { - expectNotNull(clientProvider.getClient("not-configured")); - expectFalse(clientProvider.getClient("not-configured").isPresent()); + expectNotNull(clientProvider.getById("not-configured")); + expectFalse(clientProvider.getById("not-configured").isPresent()); } @PluginTest public void getClientConfigTestConnection(FhirClientProvider clientProvider) throws Exception { - clientProvider.getClient("dsf-fhir-server").ifPresent(client -> + clientProvider.getById("dsf-fhir-server").ifPresent(client -> { CapabilityStatement statement = client.capabilities().ofType(CapabilityStatement.class).execute(); expectNotNull(statement); @@ -72,7 +72,7 @@ public void getClientConfigTestConnection(FhirClientProvider clientProvider) thr @PluginTest public void getClientConfigTestConnectionViaEndpointIdentifier(FhirClientProvider clientProvider) throws Exception { - clientProvider.getClient("#Test_Endpoint").ifPresent(client -> + clientProvider.getById("#Test_Endpoint").ifPresent(client -> { CapabilityStatement statement = client.capabilities().ofType(CapabilityStatement.class).execute(); expectNotNull(statement); diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswer.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswer.java index b891eb456..33d4b3758 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswer.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswer.java @@ -95,7 +95,7 @@ public void updateQuestionnaireResponse(ProcessPluginApi api) throws Exception Thread.sleep(Duration.ofMillis(250)); logger.info("searching ..."); - resultBundle = api.getDsfClientProvider().getLocalDsfClient().search(QuestionnaireResponse.class, + resultBundle = api.getDsfClientProvider().getLocal().search(QuestionnaireResponse.class, Map.of("status", List.of(QuestionnaireResponseStatus.INPROGRESS.toCode()))); if (resultBundle != null && resultBundle.getTotal() == 1) @@ -222,7 +222,7 @@ else if ("identifiers".equals(type)) expectTrue(update(api, qr, "dic-user", "dic-user@test.org")); } else - api.getDsfClientProvider().getLocalDsfClient().update(qr); + api.getDsfClientProvider().getLocal().update(qr); } private void set(QuestionnaireResponseItemComponent item, Type value) @@ -235,7 +235,7 @@ private boolean update(ProcessPluginApi api, QuestionnaireResponse qr, String cl qr.setAuthor(null).getAuthor().setType(ResourceType.Practitioner.name()) .setIdentifier(NamingSystems.PractitionerIdentifier.withValue(identifierValue)); - Optional oClient = api.getFhirClientProvider().getClient(clientId); + Optional oClient = api.getFhirClientProvider().getById(clientId); expectTrue(oClient.isPresent()); diff --git a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswerCheck.java b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswerCheck.java index 1dacaa325..d38a624e7 100644 --- a/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswerCheck.java +++ b/dsf-bpe/dsf-bpe-test-plugin-v2/src/main/java/dev/dsf/bpe/test/service/QuestionnaireTestAnswerCheck.java @@ -86,7 +86,7 @@ public void execute(ProcessPluginApi api, Variables variables) throws ErrorBound @PluginTest public void checkQuestionnaireResponse(ProcessPluginApi api) throws Exception { - Bundle resultBundle = api.getDsfClientProvider().getLocalDsfClient().search(QuestionnaireResponse.class, + Bundle resultBundle = api.getDsfClientProvider().getLocal().search(QuestionnaireResponse.class, Map.of("status", List.of(QuestionnaireResponseStatus.AMENDED.toCode()))); expectNotNull(resultBundle); @@ -205,7 +205,7 @@ private void test(QuestionnaireResponseItemComponent item, Type expected) private boolean read(ProcessPluginApi api, IdType id, String clientId) { - Optional oClient = api.getFhirClientProvider().getClient(clientId); + Optional oClient = api.getFhirClientProvider().getById(clientId); expectTrue(oClient.isPresent()); diff --git a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java index a0a3c1a52..8f074720d 100644 --- a/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java +++ b/dsf-common/dsf-common-jetty/src/main/java/dev/dsf/common/config/AbstractJettyConfig.java @@ -158,7 +158,7 @@ public abstract class AbstractJettyConfig extends AbstractCertificateConfig @Value("${dev.dsf.server.auth.oidc.provider.realm.base.url:#{null}}") private String oidcProviderRealmBaseUrl; - @Documentation(description = "OIDC provider dicovery path") + @Documentation(description = "OIDC provider discovery path") @Value("${dev.dsf.server.auth.oidc.provider.discovery.path:/.well-known/openid-configuration}") private String oidcProviderDiscoveryPath; @@ -214,7 +214,7 @@ public abstract class AbstractJettyConfig extends AbstractCertificateConfig @Value("${dev.dsf.proxy.password:#{null}}") private char[] proxyPassword; - @Documentation(description = "Forward proxy no-proxy list, entries will match exactly or agianst (one level) sub-domains, if no port is specified - all ports are matched; comma or space separated list, YAML block scalars supported", example = "foo.bar, test.com:8080") + @Documentation(description = "Forward proxy no-proxy list, entries will match exactly or against (one level) sub-domains, if no port is specified - all ports are matched; comma or space separated list, YAML block scalars supported", example = "foo.bar, test.com:8080") @Value("#{'${dev.dsf.proxy.noProxy:}'.trim().split('(,[ ]?)|(\\\\n)')}") private List proxyNoProxy; diff --git a/dsf-docker-dev-setup-3dic-ttp/docker-compose.yml b/dsf-docker-dev-setup-3dic-ttp/docker-compose.yml index 42329c72e..505cb386b 100644 --- a/dsf-docker-dev-setup-3dic-ttp/docker-compose.yml +++ b/dsf-docker-dev-setup-3dic-ttp/docker-compose.yml @@ -474,7 +474,7 @@ services: secrets: - db_liquibase.password - db_dic1_bpe_user.password - - db_dic1_bpe_user_camunda.password + - db_dic1_bpe_user_engine.password - root_ca.crt - dic1.crt - dic1.key @@ -507,15 +507,15 @@ services: DEV_DSF_BPE_DB_URL: jdbc:postgresql://db/dic1_bpe DEV_DSF_BPE_DB_LIQUIBASE_PASSWORD_FILE: /run/secrets/db_liquibase.password DEV_DSF_BPE_DB_USER_PASSWORD_FILE: /run/secrets/db_dic1_bpe_user.password - DEV_DSF_BPE_DB_USER_CAMUNDA_PASSWORD_FILE: /run/secrets/db_dic1_bpe_user_camunda.password + DEV_DSF_BPE_DB_USER_ENGINE_PASSWORD_FILE: /run/secrets/db_dic1_bpe_user_engine.password DEV_DSF_BPE_FHIR_CLIENT_TRUST_SERVER_CERTIFICATE_CAS: /run/secrets/root_ca.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE: /run/secrets/dic1.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE_PRIVATE_KEY: /run/secrets/dic1.key DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE_PRIVATE_KEY_PASSWORD: 'password' DEV_DSF_BPE_DB_USER_GROUP: dic1_bpe_users DEV_DSF_BPE_DB_USER_USERNAME: dic1_bpe_server_user - DEV_DSF_BPE_DB_USER_CAMUNDA_GROUP: dic1_camunda_users - DEV_DSF_BPE_DB_USER_CAMUNDA_USERNAME: dic1_camunda_server_user + DEV_DSF_BPE_DB_USER_ENGINE_GROUP: dic1_engine_users + DEV_DSF_BPE_DB_USER_ENGINE_USERNAME: dic1_engine_server_user DEV_DSF_BPE_SERVER_BASE_URL: https://dic1/bpe DEV_DSF_BPE_SERVER_UI_THEME: dev DEV_DSF_BPE_FHIR_SERVER_BASE_URL: https://dic1/fhir @@ -594,7 +594,7 @@ services: secrets: - db_liquibase.password - db_dic2_bpe_user.password - - db_dic2_bpe_user_camunda.password + - db_dic2_bpe_user_engine.password - root_ca.crt - dic2.crt - dic2.key @@ -627,15 +627,15 @@ services: DEV_DSF_BPE_DB_URL: jdbc:postgresql://db/dic2_bpe DEV_DSF_BPE_DB_LIQUIBASE_PASSWORD_FILE: /run/secrets/db_liquibase.password DEV_DSF_BPE_DB_USER_PASSWORD_FILE: /run/secrets/db_dic2_bpe_user.password - DEV_DSF_BPE_DB_USER_CAMUNDA_PASSWORD_FILE: /run/secrets/db_dic2_bpe_user_camunda.password + DEV_DSF_BPE_DB_USER_ENGINE_PASSWORD_FILE: /run/secrets/db_dic2_bpe_user_engine.password DEV_DSF_BPE_FHIR_CLIENT_TRUST_SERVER_CERTIFICATE_CAS: /run/secrets/root_ca.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE: /run/secrets/dic2.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE_PRIVATE_KEY: /run/secrets/dic2.key DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE_PRIVATE_KEY_PASSWORD: 'password' DEV_DSF_BPE_DB_USER_GROUP: dic2_bpe_users DEV_DSF_BPE_DB_USER_USERNAME: dic2_bpe_server_user - DEV_DSF_BPE_DB_USER_CAMUNDA_GROUP: dic2_camunda_users - DEV_DSF_BPE_DB_USER_CAMUNDA_USERNAME: dic2_camunda_server_user + DEV_DSF_BPE_DB_USER_ENGINE_GROUP: dic2_engine_users + DEV_DSF_BPE_DB_USER_ENGINE_USERNAME: dic2_engine_server_user DEV_DSF_BPE_SERVER_BASE_URL: https://dic2/bpe DEV_DSF_BPE_SERVER_UI_THEME: dev DEV_DSF_BPE_FHIR_SERVER_BASE_URL: https://dic2/fhir @@ -683,7 +683,7 @@ services: secrets: - db_liquibase.password - db_dic3_bpe_user.password - - db_dic3_bpe_user_camunda.password + - db_dic3_bpe_user_engine.password - root_ca.crt - dic3.crt - dic3.key @@ -716,15 +716,15 @@ services: DEV_DSF_BPE_DB_URL: jdbc:postgresql://db/dic3_bpe DEV_DSF_BPE_DB_LIQUIBASE_PASSWORD_FILE: /run/secrets/db_liquibase.password DEV_DSF_BPE_DB_USER_PASSWORD_FILE: /run/secrets/db_dic3_bpe_user.password - DEV_DSF_BPE_DB_USER_CAMUNDA_PASSWORD_FILE: /run/secrets/db_dic3_bpe_user_camunda.password + DEV_DSF_BPE_DB_USER_ENGINE_PASSWORD_FILE: /run/secrets/db_dic3_bpe_user_engine.password DEV_DSF_BPE_FHIR_CLIENT_TRUST_SERVER_CERTIFICATE_CAS: /run/secrets/root_ca.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE: /run/secrets/dic3.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE_PRIVATE_KEY: /run/secrets/dic3.key DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE_PRIVATE_KEY_PASSWORD: 'password' DEV_DSF_BPE_DB_USER_GROUP: dic3_bpe_users DEV_DSF_BPE_DB_USER_USERNAME: dic3_bpe_server_user - DEV_DSF_BPE_DB_USER_CAMUNDA_GROUP: dic3_camunda_users - DEV_DSF_BPE_DB_USER_CAMUNDA_USERNAME: dic3_camunda_server_user + DEV_DSF_BPE_DB_USER_ENGINE_GROUP: dic3_engine_users + DEV_DSF_BPE_DB_USER_ENGINE_USERNAME: dic3_engine_server_user DEV_DSF_BPE_SERVER_BASE_URL: https://dic3/bpe DEV_DSF_BPE_SERVER_UI_THEME: dev DEV_DSF_BPE_FHIR_SERVER_BASE_URL: https://dic3/fhir @@ -772,7 +772,7 @@ services: secrets: - db_liquibase.password - db_ttp_bpe_user.password - - db_ttp_bpe_user_camunda.password + - db_ttp_bpe_user_engine.password - root_ca.crt - ttp.crt - ttp.key @@ -805,15 +805,15 @@ services: DEV_DSF_BPE_DB_URL: jdbc:postgresql://db/ttp_bpe DEV_DSF_BPE_DB_LIQUIBASE_PASSWORD_FILE: /run/secrets/db_liquibase.password DEV_DSF_BPE_DB_USER_PASSWORD_FILE: /run/secrets/db_ttp_bpe_user.password - DEV_DSF_BPE_DB_USER_CAMUNDA_PASSWORD_FILE: /run/secrets/db_ttp_bpe_user_camunda.password + DEV_DSF_BPE_DB_USER_ENGINE_PASSWORD_FILE: /run/secrets/db_ttp_bpe_user_engine.password DEV_DSF_BPE_FHIR_CLIENT_TRUST_SERVER_CERTIFICATE_CAS: /run/secrets/root_ca.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE: /run/secrets/ttp.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE_PRIVATE_KEY: /run/secrets/ttp.key DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE_PRIVATE_KEY_PASSWORD_FILE: /run/secrets/ttp.key.password DEV_DSF_BPE_DB_USER_GROUP: ttp_bpe_users DEV_DSF_BPE_DB_USER_USERNAME: ttp_bpe_server_user - DEV_DSF_BPE_DB_USER_CAMUNDA_GROUP: ttp_camunda_users - DEV_DSF_BPE_DB_USER_CAMUNDA_USERNAME: ttp_camunda_server_user + DEV_DSF_BPE_DB_USER_ENGINE_GROUP: TTP_ENGINE_USERS + DEV_DSF_BPE_DB_USER_ENGINE_USERNAME: ttp_engine_server_user DEV_DSF_BPE_SERVER_BASE_URL: https://ttp/bpe DEV_DSF_BPE_SERVER_UI_THEME: dev DEV_DSF_BPE_FHIR_SERVER_BASE_URL: https://ttp/fhir @@ -905,8 +905,8 @@ secrets: db_dic1_bpe_user.password: file: ./secrets/db_dic1_bpe_user.password - db_dic1_bpe_user_camunda.password: - file: ./secrets/db_dic1_bpe_user_camunda.password + db_dic1_bpe_user_engine.password: + file: ./secrets/db_dic1_bpe_user_engine.password db_dic1_fhir_user.password: file: ./secrets/db_dic1_fhir_user.password db_dic1_fhir_user_permanent_delete.password: @@ -914,8 +914,8 @@ secrets: db_dic2_bpe_user.password: file: ./secrets/db_dic2_bpe_user.password - db_dic2_bpe_user_camunda.password: - file: ./secrets/db_dic2_bpe_user_camunda.password + db_dic2_bpe_user_engine.password: + file: ./secrets/db_dic2_bpe_user_engine.password db_dic2_fhir_user.password: file: ./secrets/db_dic2_fhir_user.password db_dic2_fhir_user_permanent_delete.password: @@ -923,8 +923,8 @@ secrets: db_dic3_bpe_user.password: file: ./secrets/db_dic3_bpe_user.password - db_dic3_bpe_user_camunda.password: - file: ./secrets/db_dic3_bpe_user_camunda.password + db_dic3_bpe_user_engine.password: + file: ./secrets/db_dic3_bpe_user_engine.password db_dic3_fhir_user.password: file: ./secrets/db_dic3_fhir_user.password db_dic3_fhir_user_permanent_delete.password: @@ -932,8 +932,8 @@ secrets: db_ttp_bpe_user.password: file: ./secrets/db_ttp_bpe_user.password - db_ttp_bpe_user_camunda.password: - file: ./secrets/db_ttp_bpe_user_camunda.password + db_ttp_bpe_user_engine.password: + file: ./secrets/db_ttp_bpe_user_engine.password db_ttp_fhir_user.password: file: ./secrets/db_ttp_fhir_user.password db_ttp_fhir_user_permanent_delete.password: diff --git a/dsf-docker-dev-setup-3dic-ttp/secrets/db_dic1_bpe_user_camunda.password b/dsf-docker-dev-setup-3dic-ttp/secrets/db_dic1_bpe_user_engine.password similarity index 100% rename from dsf-docker-dev-setup-3dic-ttp/secrets/db_dic1_bpe_user_camunda.password rename to dsf-docker-dev-setup-3dic-ttp/secrets/db_dic1_bpe_user_engine.password diff --git a/dsf-docker-dev-setup-3dic-ttp/secrets/db_dic2_bpe_user_camunda.password b/dsf-docker-dev-setup-3dic-ttp/secrets/db_dic2_bpe_user_engine.password similarity index 100% rename from dsf-docker-dev-setup-3dic-ttp/secrets/db_dic2_bpe_user_camunda.password rename to dsf-docker-dev-setup-3dic-ttp/secrets/db_dic2_bpe_user_engine.password diff --git a/dsf-docker-dev-setup-3dic-ttp/secrets/db_dic3_bpe_user_camunda.password b/dsf-docker-dev-setup-3dic-ttp/secrets/db_dic3_bpe_user_engine.password similarity index 100% rename from dsf-docker-dev-setup-3dic-ttp/secrets/db_dic3_bpe_user_camunda.password rename to dsf-docker-dev-setup-3dic-ttp/secrets/db_dic3_bpe_user_engine.password diff --git a/dsf-docker-dev-setup-3dic-ttp/secrets/db_ttp_bpe_user_camunda.password b/dsf-docker-dev-setup-3dic-ttp/secrets/db_ttp_bpe_user_engine.password similarity index 100% rename from dsf-docker-dev-setup-3dic-ttp/secrets/db_ttp_bpe_user_camunda.password rename to dsf-docker-dev-setup-3dic-ttp/secrets/db_ttp_bpe_user_engine.password diff --git a/dsf-docker-dev-setup/bpe/docker-compose.yml b/dsf-docker-dev-setup/bpe/docker-compose.yml index 4c96b9ca6..b4e2e0ba1 100644 --- a/dsf-docker-dev-setup/bpe/docker-compose.yml +++ b/dsf-docker-dev-setup/bpe/docker-compose.yml @@ -53,7 +53,7 @@ services: secrets: - db_liquibase.password - db_user.password - - db_user_camunda.password + - db_user_engine.password - root_ca.crt - bpe.crt - bpe.key @@ -73,7 +73,7 @@ services: DEV_DSF_BPE_DB_URL: jdbc:postgresql://db/bpe DEV_DSF_BPE_DB_LIQUIBASE_PASSWORD_FILE: /run/secrets/db_liquibase.password DEV_DSF_BPE_DB_USER_PASSWORD_FILE: /run/secrets/db_user.password - DEV_DSF_BPE_DB_USER_CAMUNDA_PASSWORD_FILE: /run/secrets/db_user_camunda.password + DEV_DSF_BPE_DB_USER_ENGINE_PASSWORD_FILE: /run/secrets/db_user_engine.password DEV_DSF_BPE_FHIR_CLIENT_TRUST_SERVER_CERTIFICATE_CAS: /run/secrets/root_ca.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE: /run/secrets/bpe.crt DEV_DSF_BPE_FHIR_CLIENT_CERTIFICATE_PRIVATE_KEY: /run/secrets/bpe.key @@ -136,8 +136,8 @@ secrets: file: ./secrets/db_liquibase.password db_user.password: file: ./secrets/db_user.password - db_user_camunda.password: - file: ./secrets/db_user_camunda.password + db_user_engine.password: + file: ./secrets/db_user_engine.password networks: frontend: diff --git a/dsf-docker-dev-setup/bpe/secrets/db_user_camunda.password b/dsf-docker-dev-setup/bpe/secrets/db_user_engine.password similarity index 100% rename from dsf-docker-dev-setup/bpe/secrets/db_user_camunda.password rename to dsf-docker-dev-setup/bpe/secrets/db_user_engine.password diff --git a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/FhirAdapter.java b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/FhirAdapter.java index 607303f4b..93035d624 100755 --- a/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/FhirAdapter.java +++ b/dsf-fhir/dsf-fhir-rest-adapter/src/main/java/dev/dsf/fhir/adapter/FhirAdapter.java @@ -140,7 +140,8 @@ private void writeBinary(MediaType mediaType, OutputStream entityStream, Binary writer.write(split[0]); writer.flush(); - Base64OutputStream base64 = new Base64OutputStream(entityStream, true, Integer.MIN_VALUE, null); + Base64OutputStream base64 = Base64OutputStream.builder().setOutputStream(entityStream).setEncode(true) + .get(); data.writeExternal(base64); base64.eof(); base64.flush(); @@ -191,7 +192,8 @@ private void writeBundleWithBinary(MediaType mediaType, OutputStream entityStrea writer.write(split[0]); writer.flush(); - Base64OutputStream base64 = new Base64OutputStream(entityStream, true, Integer.MIN_VALUE, null); + Base64OutputStream base64 = Base64OutputStream.builder().setOutputStream(entityStream) + .setEncode(true).get(); data.writeExternal(base64); base64.eof(); base64.flush(); diff --git a/dsf-fhir/dsf-fhir-server-jetty/pom.xml b/dsf-fhir/dsf-fhir-server-jetty/pom.xml index b622fd9cd..394a8ca7b 100755 --- a/dsf-fhir/dsf-fhir-server-jetty/pom.xml +++ b/dsf-fhir/dsf-fhir-server-jetty/pom.xml @@ -130,7 +130,6 @@ docker/lib compile - camunda-bpmn-model,camunda-xml-model diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java index 5ba3ee010..140f516cb 100644 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/spring/config/PropertiesConfig.java @@ -91,7 +91,7 @@ public class PropertiesConfig extends AbstractCertificateConfig implements Initi @Value("${dev.dsf.fhir.server.page.count:20}") private int defaultPageCount; - @Documentation(description = "UI theme parameter, adds a color indicator to the ui to distinguish `dev`, `test` and `prod` environments im configured; supported values: `dev`, `test` and `prod`") + @Documentation(description = "UI theme parameter, adds a color indicator to the ui to distinguish `dev`, `test` and `prod` environments if configured; supported values: `dev`, `test` and `prod`") @Value("${dev.dsf.fhir.server.ui.theme:}") private String uiTheme; diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/RootServiceImpl.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/RootServiceImpl.java index 10d5a2d53..77c714877 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/RootServiceImpl.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/webservice/impl/RootServiceImpl.java @@ -33,6 +33,7 @@ import dev.dsf.fhir.webservice.base.AbstractBasicService; import dev.dsf.fhir.webservice.specification.RootService; import jakarta.ws.rs.HttpMethod; +import jakarta.ws.rs.core.CacheControl; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; @@ -42,6 +43,13 @@ public class RootServiceImpl extends AbstractBasicService implements RootService { public static final String ROOT_GET = RootServiceImpl.class.getCanonicalName() + ".get"; + private static final CacheControl NO_STORE = new CacheControl(); + static + { + // no-transform set by default + NO_STORE.setNoStore(true); // never store this response + } + private final CommandFactory commandFactory; private final ResponseGenerator responseGenerator; private final ParameterConverter parameterConverter; @@ -82,7 +90,7 @@ public Response root(UriInfo uri, HttpHeaders headers) return responseGenerator .response(Status.METHOD_NOT_ALLOWED, outcome, parameterConverter.getMediaTypeThrowIfNotSupported(uri, headers)) - .allow(HttpMethod.POST).build(); + .allow(HttpMethod.POST).cacheControl(NO_STORE).build(); } @Override diff --git a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/websocket/ServerEndpoint.java b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/websocket/ServerEndpoint.java index a4601ffa7..7abda59bd 100755 --- a/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/websocket/ServerEndpoint.java +++ b/dsf-fhir/dsf-fhir-server/src/main/java/dev/dsf/fhir/websocket/ServerEndpoint.java @@ -53,7 +53,8 @@ public class ServerEndpoint extends Endpoint implements InitializingBean, Dispos private static final String PINGER_PROPERTY = ServerEndpoint.class.getName() + ".pinger"; private static final String BIND_MESSAGE_START = "bind "; - private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2, + r -> new Thread(r, "websocket-server-endpoint-scheduler")); private final WebSocketSubscriptionManager subscriptionManager; diff --git a/dsf-fhir/dsf-fhir-validation/src/main/java/dev/dsf/fhir/validation/ResourceValidatorImpl.java b/dsf-fhir/dsf-fhir-validation/src/main/java/dev/dsf/fhir/validation/ResourceValidatorImpl.java index 436c0fc06..062737dd6 100755 --- a/dsf-fhir/dsf-fhir-validation/src/main/java/dev/dsf/fhir/validation/ResourceValidatorImpl.java +++ b/dsf-fhir/dsf-fhir-validation/src/main/java/dev/dsf/fhir/validation/ResourceValidatorImpl.java @@ -27,6 +27,7 @@ import org.hl7.fhir.common.hapi.validation.validator.WorkerContextValidationSupportAdapter; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Resource; import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalType; @@ -196,10 +197,13 @@ public ValidationResult validate(Resource resource) { ValidationResult result = validator.validateWithResult(resource); - // TODO: remove after HAPI validator is fixed: https://github.com/hapifhir/org.hl7.fhir.core/issues/193 + // TODO remove after HAPI validator is fixed: https://github.com/hapifhir/org.hl7.fhir.core/issues/193 adaptDefaultSliceValidationErrorToWarning(result); adaptQuestionnaireTextNotSameValidationErrorToWarning(result); + // TODO remove if MII plugins with error no longer in circulation + adaptMiiPluginsBundleFullUrlUuidNullErrorToWarning(resource, result); + return new ValidationResult(context, result.getMessages().stream().filter(m -> !(ResultSeverityEnum.WARNING.equals(m.getSeverity()) && m.getMessage().startsWith(MISSING_NARRATIVE_MESSAGE_START))).toList()); @@ -220,4 +224,18 @@ private void adaptQuestionnaireTextNotSameValidationErrorToWarning(ValidationRes .startsWith("If text exists, it must match the questionnaire definition for linkId")) .forEach(m -> m.setSeverity(ResultSeverityEnum.WARNING)); } + + private void adaptMiiPluginsBundleFullUrlUuidNullErrorToWarning(Resource resource, ValidationResult result) + { + if (resource instanceof Bundle b && b.hasIdentifier() + && "http://medizininformatik-initiative.de/fhir/CodeSystem/cryptography" + .equals(b.getIdentifier().getSystem()) + && "public-key".equals(b.getIdentifier().getValue())) + { + result.getMessages().stream() + .filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + && "UUIDs must be valid and lowercase (null)".equals(m.getMessage())) + .forEach(m -> m.setSeverity(ResultSeverityEnum.WARNING)); + } + } } diff --git a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-bpmn-message-2.0.0.xml b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-bpmn-message-2.0.0.xml index 33b5b155a..2a626e6a2 100644 --- a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-bpmn-message-2.0.0.xml +++ b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-bpmn-message-2.0.0.xml @@ -46,7 +46,7 @@ - + diff --git a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-practitioner-role-2.0.0.xml b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-practitioner-role-2.0.0.xml index cf1fb2eab..72a96b85d 100644 --- a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-practitioner-role-2.0.0.xml +++ b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/CodeSystem/dsf-practitioner-role-2.0.0.xml @@ -37,50 +37,62 @@ - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + + + + diff --git a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-organization-affiliation-2.0.0.xml b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-organization-affiliation-2.0.0.xml index 516aff4a0..c4390fd00 100644 --- a/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-organization-affiliation-2.0.0.xml +++ b/dsf-fhir/dsf-fhir-validation/src/main/resources/fhir/StructureDefinition/dsf-organization-affiliation-2.0.0.xml @@ -79,6 +79,10 @@ + + + + diff --git a/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/BundleProfileTest.java b/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/BundleProfileTest.java index 6ba96040d..9f160dd27 100644 --- a/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/BundleProfileTest.java +++ b/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/BundleProfileTest.java @@ -16,9 +16,13 @@ package dev.dsf.fhir.profiles; import java.util.List; +import java.util.UUID; +import org.hl7.fhir.r4.model.Binary; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleType; +import org.hl7.fhir.r4.model.DocumentReference; +import org.hl7.fhir.r4.model.Enumerations.DocumentReferenceStatus; import org.junit.ClassRule; import org.junit.Test; @@ -54,4 +58,23 @@ public void runMetaTagTests() throws Exception { doRunMetaTagTests(resourceValidator); } + + @Test + public void testBundleWithUrnUuidNull() throws Exception + { + Binary binary = new Binary().setContentType("text/plain"); + + String binaryUuid = "urn:uuid:" + UUID.randomUUID().toString(); + + DocumentReference documentReference = new DocumentReference().setStatus(DocumentReferenceStatus.CURRENT); + documentReference.addContent().getAttachment().setContentType("text/plain").setUrl(binaryUuid); + + Bundle bundle = new Bundle().setType(BundleType.COLLECTION); + bundle.getIdentifier().setSystem("http://medizininformatik-initiative.de/fhir/CodeSystem/cryptography") + .setValue("public-key"); + bundle.addEntry().setResource(documentReference).setFullUrl("urn:uuid:null"); + bundle.addEntry().setResource(binary).setFullUrl(binaryUuid); + + testValid(resourceValidator, bundle); + } } diff --git a/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/OrganizationAffiliationProfileTest.java b/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/OrganizationAffiliationProfileTest.java index 23c49aaee..7e82b7b4e 100644 --- a/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/OrganizationAffiliationProfileTest.java +++ b/dsf-fhir/dsf-fhir-validation/src/test/java/dev/dsf/fhir/profiles/OrganizationAffiliationProfileTest.java @@ -102,4 +102,20 @@ public void testOrganizationAffiliationProfileNotValidMultipleEndpoints() throws assertEquals(1, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); } + + @Test + public void testOrganizationAffiliationProfileNoDsfOrganizationRole() throws Exception + { + OrganizationAffiliation a = create(); + a.getMeta().addTag().setSystem("http://dsf.dev/fhir/CodeSystem/read-access-tag").setCode("ALL"); + + a.getCodeFirstRep().getCodingFirstRep().setSystem("http://test.org/fhir/CodeSystem/special-role") + .setCode("Foo"); + + ValidationResult result = resourceValidator.validate(a); + ValidationSupportRule.logValidationMessages(logger::debug, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } } diff --git a/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/config/ConfigDocGenerator.java b/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/config/ConfigDocGenerator.java index ccaeddc94..8938be0e2 100644 --- a/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/config/ConfigDocGenerator.java +++ b/dsf-maven/dsf-maven-plugin/src/main/java/dev/dsf/maven/config/ConfigDocGenerator.java @@ -393,8 +393,9 @@ private Function dsfDocumentationGenerator() getString(documentation, "recommendation")); String example = getDocumentationStringMonospace("Example", getString(documentation, "example")); - String defaultValueString = devalueValue != null && devalueValue.length() > 1 - && !"#{null}".equals(devalueValue) ? getDocumentationStringMonospace("Default", devalueValue) : ""; + String defaultValueString = devalueValue != null && !"#{null}".equals(devalueValue) + ? getDocumentationStringMonospace("Default", devalueValue) + : ""; return new DocumentationEntry(propertyName, String.format("### %s%n%s%s%s%s%s%s%n", environment, propertyString, required, description, @@ -432,7 +433,7 @@ private String getString(Object coreDocumentationAnnotation, String methodName) private String getDocumentationStringMonospace(String title, String value) { - if (title == null || title.isBlank() || value == null || value.isBlank()) + if (title == null || title.isBlank()) return ""; return String.format("- **%s:** `%s`%n", title, value); diff --git a/pom.xml b/pom.xml index 3af11e0b2..06331cff8 100755 --- a/pom.xml +++ b/pom.xml @@ -40,12 +40,12 @@ 2.0.17 2.25.2 - 12.1.3 + 12.1.4 3.1.11 2.2.1 - 6.2.12 + 6.2.13 2.20.1 - 1.0.0-rc-2 + 1.0.0 5.1.0 8.4.0 6.5.27 @@ -419,7 +419,7 @@ org.apache.commons commons-lang3 - 3.19.0 + 3.20.0 org.apache.httpcomponents @@ -450,12 +450,12 @@ commons-io commons-io - 2.20.0 + 2.21.0 commons-codec commons-codec - 1.19.0 + 1.20.0 @@ -528,7 +528,7 @@ org.apache.maven.plugins maven-jar-plugin - 3.4.2 + 3.5.0 diff --git a/src/main/resources/templates/java-test-bpe-config.properties b/src/main/resources/templates/java-test-bpe-config.properties index e88613239..a20b006b1 100644 --- a/src/main/resources/templates/java-test-bpe-config.properties +++ b/src/main/resources/templates/java-test-bpe-config.properties @@ -17,7 +17,7 @@ dev.dsf.bpe.db.url=jdbc:postgresql://localhost/bpe dev.dsf.bpe.db.liquibase.password=fLp6ZSd5QrMAkGZMjxqXjmcWrTfa3Dn8fA57h92Y dev.dsf.bpe.db.user.password=as2hm56BPcaJKtG25JEx -dev.dsf.bpe.db.user.camunda.password=arpJ2FgJuYvUJhbxeuh7 +dev.dsf.bpe.db.user.engine.password=arpJ2FgJuYvUJhbxeuh7 dev.dsf.bpe.fhir.server.organization.identifier.value=Test_Organization