diff --git a/README.md b/README.md index 0d055bce..be2f66d6 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,9 @@ In this repository you will find the ping-pong process for connection testing, which can be deployed on the [DSF](https://github.com/datasharingframework/dsf). +## Version 2.x release +Version 2.x of the ping-pong process (dubbed "Fat-Ping") includes the ability to test whether downloading FHIR resources from other DSF instances is possible. This also includes approximately measuring the network speed of resource downloads with larger download sizes (~100MB) returning more accurate results. It retains the ability to make connection tests without downloading resource like ping-pong 1.x. Documentation on configuration is available in the [wiki](https://github.com/datasharingframework/dsf-process-ping-pong/wiki). + ## Development Branching follows the git-flow model, for the latest development version see branch [develop](https://github.com/datasharingframework/dsf-process-ping-pong/tree/develop). diff --git a/eclipse-formatter-config.xml b/eclipse-formatter-config.xml index 0c015e33..f87f179d 100644 --- a/eclipse-formatter-config.xml +++ b/eclipse-formatter-config.xml @@ -1,380 +1,402 @@ - - + + - - - - - - - - - - - - - - + - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - + - - - - - - - - - - + - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - + - - - - - - + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + - - - - - - - - - - - - - - - - - diff --git a/pom.xml b/pom.xml index b69dde88..7af530e6 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ 17 17 - 1.2.0 + 1.8.0 ../dsf @@ -56,6 +56,26 @@ provided + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.19.0 + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-annotations + + + + dev.dsf @@ -115,6 +135,25 @@ org.apache.maven.plugins maven-shade-plugin 3.5.0 + + + package + + shade + + + false + + + com.fasterxml.jackson.datatype:jackson-datatype-jsr310 + + ** + + + + + + org.apache.maven.plugins diff --git a/src/main/java/dev/dsf/bpe/CodeSystem.java b/src/main/java/dev/dsf/bpe/CodeSystem.java new file mode 100644 index 00000000..b06d884e --- /dev/null +++ b/src/main/java/dev/dsf/bpe/CodeSystem.java @@ -0,0 +1,766 @@ +package dev.dsf.bpe; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.hl7.fhir.r4.model.Coding; + +public final class CodeSystem +{ + private CodeSystem() + { + } + + interface SingleStringValueEnum + { + String getValue(); + } + + protected static class SingleStringValueEnumParser & SingleStringValueEnum> + { + private final Class enumClass; + + public SingleStringValueEnumParser(Class enumClass) + { + this.enumClass = enumClass; + } + + public T ofValue(String value) + { + try + { + return T.valueOf(enumClass, value); + } + catch (IllegalArgumentException e) + { + for (T t : enumClass.getEnumConstants()) + { + if (t.getValue().equals(value)) + { + return t; + } + } + throw new IllegalArgumentException("Unable to convert " + value + " to " + enumClass.getName()); + } + } + } + + public static final class DsfPing + { + public static final String URL = "http://dsf.dev/fhir/CodeSystem/ping"; + + private DsfPing() + { + } + + public static Coding fromCode(Code code) + { + return new Coding().setSystem(URL).setCode(code.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); + } + + public enum Code implements SingleStringValueEnum + { + PING_STATUS("ping-status"), + PONG_STATUS("pong-status"), + ENDPOINT_IDENTIFIER("endpoint-identifier"), + TARGET_ENDPOINTS("target-endpoints"), + TIMER_INTERVAL("timer-interval"), + DOWNLOAD_RESOURCE_SIZE_BYTES("download-resource-size-bytes"), + DOWNLOADED_DURATION_MILLIS("downloaded-duration"), + DOWNLOADED_BYTES("downloaded-bytes"), + DOWNLOAD_RESOURCE_REFERENCE("download-resource-reference"), + ERROR("error"), + PONG_TIMEOUT_DURATION_ISO_8601("pong-timeout-duration"); + + private final String value; + + Code(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + + public static Code ofValue(String value) + { + return new SingleStringValueEnumParser<>(Code.class).ofValue(value); + } + } + } + + public static final class DsfPingStatus + { + public static final String URL = "http://dsf.dev/fhir/CodeSystem/ping-status"; + + private DsfPingStatus() + { + } + + public static Coding fromCode(Code code) + { + return new Coding().setSystem(URL).setCode(code.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); + } + + public enum Code implements SingleStringValueEnum + { + NOT_ALLOWED("not-allowed"), + NOT_REACHABLE("not-reachable"), + PONG_MISSING("pong-missing"), + PONG_RECEIVED("pong-received"), + PONG_SENT("pong-send"); + + private final String value; + + Code(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + + public static Code ofValue(String value) + { + return new SingleStringValueEnumParser<>(Code.class).ofValue(value); + } + } + } + + // TODO: rename to DsfNetworkSpeedUnits + public static final class DsfPingUnits + { + public static final String URL = "http://dsf.dev/fhir/CodeSystem/ping-units"; + + private DsfPingUnits() + { + } + + public static Coding fromCode(Code code) + { + return new Coding().setSystem(URL).setCode(code.toUcum()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); + } + + public enum Code + { + bps + { + @Override + public BigDecimal calculateSpeed(long bytes, Duration duration) + { + if (bytes == 0) + return BigDecimal.ZERO; + if (duration.isZero()) + return BigDecimal.valueOf(Long.MAX_VALUE); + BigDecimal bits = BigDecimal.valueOf(bytes * 8L).setScale(2, RoundingMode.HALF_UP); + BigDecimal seconds = BigDecimal.valueOf(duration.toMillis()).setScale(3, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000).setScale(3, RoundingMode.HALF_UP), RoundingMode.HALF_UP); + return bits.divide(seconds, 2, RoundingMode.HALF_UP); + } + + @Override + public String toUcum() + { + return "bit/s"; + } + }, + kbps + { + @Override + public BigDecimal calculateSpeed(long bytes, Duration duration) + { + if (bytes == 0) + return BigDecimal.ZERO; + if (duration.isZero()) + return BigDecimal.valueOf(Long.MAX_VALUE); + BigDecimal kiloBits = BigDecimal.valueOf(bytes * 8L).setScale(2, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000), RoundingMode.HALF_UP); + BigDecimal seconds = BigDecimal.valueOf(duration.toMillis()).setScale(3, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000).setScale(3, RoundingMode.HALF_UP), RoundingMode.HALF_UP); + return kiloBits.divide(seconds, 2, RoundingMode.HALF_UP); + } + + @Override + public String toUcum() + { + return "Kbit/s"; + } + }, + Mbps + { + @Override + public BigDecimal calculateSpeed(long bytes, Duration duration) + { + if (bytes == 0) + return BigDecimal.ZERO; + if (duration.isZero()) + return BigDecimal.valueOf(Long.MAX_VALUE); + BigDecimal kiloBits = BigDecimal.valueOf(bytes * 8L).setScale(2, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000000), RoundingMode.HALF_UP); + BigDecimal seconds = BigDecimal.valueOf(duration.toMillis()).setScale(3, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000).setScale(3, RoundingMode.HALF_UP), RoundingMode.HALF_UP); + return kiloBits.divide(seconds, 2, RoundingMode.HALF_UP); + } + + @Override + public String toUcum() + { + return "Mbit/s"; + } + }, + Gbps + { + @Override + public BigDecimal calculateSpeed(long bytes, Duration duration) + { + if (bytes == 0) + return BigDecimal.ZERO; + if (duration.isZero()) + return BigDecimal.valueOf(Long.MAX_VALUE); + BigDecimal kiloBits = BigDecimal.valueOf(bytes * 8L).setScale(2, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000000000), RoundingMode.HALF_UP); + BigDecimal seconds = BigDecimal.valueOf(duration.toMillis()).setScale(3, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000).setScale(3, RoundingMode.HALF_UP), RoundingMode.HALF_UP); + return kiloBits.divide(seconds, 2, RoundingMode.HALF_UP); + } + + @Override + public String toUcum() + { + return "Gbit/s"; + } + }, + Bps + { + @Override + public BigDecimal calculateSpeed(long bytes, Duration duration) + { + if (bytes == 0) + return BigDecimal.ZERO; + if (duration.isZero()) + return BigDecimal.valueOf(Long.MAX_VALUE); + BigDecimal kiloBits = BigDecimal.valueOf(bytes).setScale(2, RoundingMode.HALF_UP); + BigDecimal seconds = BigDecimal.valueOf(duration.toMillis()).setScale(3, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000).setScale(3, RoundingMode.HALF_UP), RoundingMode.HALF_UP); + return kiloBits.divide(seconds, 2, RoundingMode.HALF_UP); + } + + @Override + public String toUcum() + { + return "By/s"; + } + }, + kBps + { + @Override + public BigDecimal calculateSpeed(long bytes, Duration duration) + { + if (bytes == 0) + return BigDecimal.ZERO; + if (duration.isZero()) + return BigDecimal.valueOf(Long.MAX_VALUE); + BigDecimal kiloBits = BigDecimal.valueOf(bytes).setScale(2, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000), RoundingMode.HALF_UP); + BigDecimal seconds = BigDecimal.valueOf(duration.toMillis()).setScale(3, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000).setScale(3, RoundingMode.HALF_UP), RoundingMode.HALF_UP); + return kiloBits.divide(seconds, 2, RoundingMode.HALF_UP); + } + + @Override + public String toUcum() + { + return "KBy/s"; + } + }, + MBps + { + @Override + public BigDecimal calculateSpeed(long bytes, Duration duration) + { + if (bytes == 0) + return BigDecimal.ZERO; + if (duration.isZero()) + return BigDecimal.valueOf(Long.MAX_VALUE); + BigDecimal kiloBits = BigDecimal.valueOf(bytes).setScale(2, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000000), RoundingMode.HALF_UP); + BigDecimal seconds = BigDecimal.valueOf(duration.toMillis()).setScale(3, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000).setScale(3, RoundingMode.HALF_UP), RoundingMode.HALF_UP); + return kiloBits.divide(seconds, 2, RoundingMode.HALF_UP); + } + + @Override + public String toUcum() + { + return "MBy/s"; + } + }, + GBps + { + @Override + public BigDecimal calculateSpeed(long bytes, Duration duration) + { + if (bytes == 0) + return BigDecimal.ZERO; + if (duration.isZero()) + return BigDecimal.valueOf(Long.MAX_VALUE); + BigDecimal kiloBits = BigDecimal.valueOf(bytes).setScale(2, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000000000), RoundingMode.HALF_UP); + BigDecimal seconds = BigDecimal.valueOf(duration.toMillis()).setScale(3, RoundingMode.HALF_UP) + .divide(BigDecimal.valueOf(1000).setScale(3, RoundingMode.HALF_UP), RoundingMode.HALF_UP); + return kiloBits.divide(seconds, 2, RoundingMode.HALF_UP); + } + + @Override + public String toUcum() + { + return "GBy/s"; + } + }; + + public abstract BigDecimal calculateSpeed(long bytes, Duration duration); + + public abstract String toUcum(); + + public static SpeedAndUnit calculateSpeedWithFittingUnit(Long downloadedBytes, Duration downloadedDuration) + { + return Stream + .of(CodeSystem.DsfPingUnits.Code.bps, CodeSystem.DsfPingUnits.Code.kbps, + CodeSystem.DsfPingUnits.Code.Mbps, CodeSystem.DsfPingUnits.Code.Gbps) + .map(unit -> new SpeedAndUnit(unit.calculateSpeed(downloadedBytes, downloadedDuration), unit)) + .filter(speedAndUnit -> speedAndUnit.speed.compareTo(BigDecimal.valueOf(1000L)) < 0).findFirst() + .orElse(new SpeedAndUnit( + CodeSystem.DsfPingUnits.Code.Gbps.calculateSpeed(downloadedBytes, downloadedDuration), + Code.Gbps)); + } + + public record SpeedAndUnit(BigDecimal speed, Code unit) + { + } + } + } + + public final class DsfPingError + { + public static final String URL = "http://dsf.dev/fhir/CodeSystem/ping-error"; + + private DsfPingError() + { + } + + public static Coding fromConcept(Concept concept) + { + return new Coding().setSystem(URL).setCode(concept.getCode()).setDisplay(concept.getDisplay()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); + } + + public enum Concept + { + SEND_MESSAGE_HTTP_401( + "send-message-http-401", + "Sending a message to the remote instance resulted in HTTP status 401" + ), + SEND_MESSAGE_HTTP_403( + "send-message-http-403", + "Sending a message to the remote instance resulted in HTTP status 403" + ), + SEND_REFERENCE_MESSAGE_HTTP_403( + "send-reference-message-http-403", + "Sending a message including a reference to the remote instance resulted in HTTP status 403" + ), + SEND_MESSAGE_HTTP_500( + "send-message-http-500", + "Sending a message to the remote instance resulted in HTTP status 500" + ), + SEND_MESSAGE_HTTP_502( + "send-message-http-502", + "Sending a message to the remote instance resulted in HTTP status 502" + ), + SEND_MESSAGE_HTTP_UNEXPECTED( + "send-message-http-unexpected", + "Sending a message to the remote instance resulted in an unexpected HTTP status code" + ), + SEND_MESSAGE_SSL_HANDSHAKE( + "send-message-ssl-handshake", + "Sending a message to the remote instance was unsuccessful because of a failed SSL handshake" + ), + SEND_MESSAGE_CONNECT_TIMEOUT( + "send-message-connect-timeout", + "Sending a message to the remote instance was unsuccessful because of a connection timeout" + ), + SEND_MESSAGE_HTTP_HOST_CONNECT( + "send-message-http-host-connect", + "Sending a message to the remote instance was unsuccessful because the connection was refused" + ), + SEND_MESSAGE_UNKNOWN_HOST( + "send-message-unknown-host", + "Sending a message to the remote instance was unsuccessful because the target hostname could not be resolved" + ), + + RECEIVE_MESSAGE_HTTP_401( + "receive-message-http-401", + "Received a message and responded with HTTP status 401" + ), + RECEIVE_MESSAGE_HTTP_403( + "receive-message-http-403", + "Received a message and responded with HTTP status 403" + ), + RECEIVE_REFERENCE_MESSAGE_HTTP_403( + "receive-reference-message-http-403", + "Received a message including a reference and responded with HTTP status 403" + ), + RECEIVE_MESSAGE_HTTP_500( + "receive-message-http-500", + "Received a message and responded with HTTP status 500" + ), + RECEIVE_MESSAGE_HTTP_502( + "receive-message-http-502", + "Received a message and responded with HTTP status 502" + ), + RECEIVE_MESSAGE_HTTP_UNEXPECTED( + "receive-message-http-unexpected", + "Received a message and responded with an unexpected HTTP status code" + ), + RECEIVE_MESSAGE_SSL_HANDSHAKE( + "receive-message-ssl-handshake", + "Receiving a message was unsuccessful because of a failed SSL handshake" + ), + RECEIVE_MESSAGE_CONNECT_TIMEOUT( + "receive-message-connect-timeout", + "Receiving a message was unsuccessful because of a connection timeout" + ), + RECEIVE_MESSAGE_HTTP_HOST_CONNECT( + "receive-message-http-host-connect", + "Receiving a message was unsuccessful because the connection was refused" + ), + RECEIVE_MESSAGE_UNKNOWN_HOST( + "receive-message-unknown-host", + "Receiving a message was unsuccessful because the target hostname could not be resolved" + ), + + LOCAL_BINARY_DELETE_TIMEOUT_CONNECT( + "local-binary-delete-timeout-connect", + "Local instance encountered a connect timeout trying to clean up the binary resource" + ), + LOCAL_BINARY_DELETE_TIMEOUT_READ( + "local-binary-delete-timeout-read", + "Local instance encountered a read timeout trying to clean up the binary resource" + ), + LOCAL_BINARY_DELETE_HTTP_HOST_CONNECT( + "local-binary-delete-http-host-connect", + "Local instance was unable to clean up the binary resource from the local DSF FHIR server because the connection was refused" + ), + LOCAL_BINARY_DELETE_HTTP_401( + "local-binary-delete-http-401", + "Local instance encountered a HTTP status 401 trying to clean up the binary resource" + ), + LOCAL_BINARY_DELETE_HTTP_403( + "local-binary-delete-http-403", + "Local instance encountered a HTTP status 403 trying to clean up the binary resource" + ), + LOCAL_BINARY_DELETE_HTTP_500( + "local-binary-delete-http-500", + "Local instance encountered a HTTP status 500 trying to clean up the binary resource" + ), + LOCAL_BINARY_DELETE_HTTP_502( + "local-binary-delete-http-502", + "Local instance encountered a HTTP status 502 trying to clean up the binary resource" + ), + LOCAL_BINARY_DELETE_HTTP_UNEXPECTED( + "local-binary-delete-http-unexpected", + "Local instance encountered an unexpected HTTP status code trying to clean up the binary resource" + ), + + REMOTE_BINARY_DELETE_TIMEOUT_CONNECT( + "remote-binary-delete-timeout-connect", + "Remote instance encountered a connect timeout trying to clean up the binary resource" + ), + REMOTE_BINARY_DELETE_TIMEOUT_READ( + "remote-binary-delete-timeout-read", + "Remote instance encountered a read timeout trying to clean up the binary resource" + ), + REMOTE_BINARY_DELETE_HTTP_HOST_CONNECT( + "remote-binary-delete-http-host-connect", + "Remote instance was unable to clean up the binary resource from its DSF FHIR server because the connection was refused" + ), + REMOTE_BINARY_DELETE_HTTP_401( + "remote-binary-delete-http-401", + "Remote instance encountered a HTTP status 401 trying to clean up the binary resource" + ), + REMOTE_BINARY_DELETE_HTTP_403( + "remote-binary-delete-http-403", + "Remote instance encountered a HTTP status 403 trying to clean up the binary resource" + ), + REMOTE_BINARY_DELETE_HTTP_500( + "remote-binary-delete-http-500", + "Remote instance encountered a HTTP status 500 trying to clean up the binary resource" + ), + REMOTE_BINARY_DELETE_HTTP_502( + "remote-binary-delete-http-502", + "Remote instance encountered a HTTP status 502 trying to clean up the binary resource" + ), + REMOTE_BINARY_DELETE_HTTP_UNEXPECTED( + "remote-binary-delete-http-unexpected", + "Remote instance encountered an unexpected HTTP status code trying to clean up the binary resource" + ), + + LOCAL_BINARY_POST_HTTP_401( + "local-binary-post-http-401", + "Local instance encountered a HTTP status 401 trying to post the binary resource to its own FHIR server" + ), + LOCAL_BINARY_POST_HTTP_403( + "local-binary-post-http-403", + "Local instance encountered a HTTP status 403 trying to post the binary resource to its own FHIR server" + ), + LOCAL_BINARY_POST_HTTP_413( + "local-binary-post-http-413", + "Local instance encountered a HTTP status 413 trying to post the binary resource to its own FHIR server" + ), + LOCAL_BINARY_POST_HTTP_500( + "local-binary-post-http-500", + "Local instance encountered a HTTP status 500 trying to post the binary resource to its own FHIR server" + ), + LOCAL_BINARY_POST_HTTP_502( + "local-binary-post-http-502", + "Local instance encountered a HTTP status 502 trying to post the binary resource to its own FHIR server" + ), + LOCAL_BINARY_POST_HTTP_UNEXPECTED( + "local-binary-post-http-unexpected", + "Local instance encountered an unexpected HTTP status code trying to post the binary resource to its own FHIR server" + ), + LOCAL_BINARY_POST_TIMEOUT_CONNECT( + "local-binary-post-timeout-connect", + "Local instance encountered a connect timeout trying to post the binary resource to its own FHIR server" + ), + LOCAL_BINARY_POST_TIMEOUT_READ( + "local-binary-post-timeout-read", + "Local instance encountered a read timeout trying to post the binary resource to its own FHIR server" + ), + LOCAL_BINARY_POST_HTTP_HOST_CONNECT( + "local-binary-post-http-host-connect", + "Local instance was unable to post the binary resource to its own DSF FHIR server because the connection was refused" + ), + + REMOTE_BINARY_POST_HTTP_401( + "remote-binary-post-http-401", + "Remote instance encountered a HTTP status 401 trying to post the binary resource to its own FHIR server" + ), + REMOTE_BINARY_POST_HTTP_403( + "remote-binary-post-http-403", + "Remote instance encountered a HTTP status 403 trying to post the binary resource to its own FHIR server" + ), + REMOTE_BINARY_POST_HTTP_413( + "remote-binary-post-http-413", + "Remote instance encountered a HTTP status 413 trying to post the binary resource to its own FHIR server" + ), + REMOTE_BINARY_POST_HTTP_500( + "remote-binary-post-http-500", + "Remote instance encountered a HTTP status 500 trying to post the binary resource to its own FHIR server" + ), + REMOTE_BINARY_POST_HTTP_502( + "remote-binary-post-http-502", + "Remote instance encountered a HTTP status 502 trying to post the binary resource to its own FHIR server" + ), + REMOTE_BINARY_POST_HTTP_UNEXPECTED( + "remote-binary-post-http-unexpected", + "Remote instance encountered an unexpected HTTP status code trying to post the binary resource to its own FHIR server" + ), + REMOTE_BINARY_POST_TIMEOUT_CONNECT( + "remote-binary-post-timeout-connect", + "Remote instance encountered a connect timeout trying to post the binary resource to its own FHIR server" + ), + REMOTE_BINARY_POST_TIMEOUT_READ( + "remote-binary-post-timeout-read", + "Remote instance encountered a read timeout trying to post the binary resource to its own FHIR server" + ), + REMOTE_BINARY_POST_HTTP_HOST_CONNECT( + "remote-binary-post-http-host-connect", + "Remote instance was unable to post the binary resource to its own DSF FHIR server because the connection was refused" + ), + + RESPONSE_MESSAGE_TIMEOUT_STATUS_REQUESTED( + "response-message-timeout-status-requested", + "Response missing, sent Task status is 'requested'" + ), + RESPONSE_MESSAGE_TIMEOUT_STATUS_IN_PROGRESS( + "response-message-timeout-status-in-progress", + "Response missing, sent Task status is 'in-progress'" + ), + RESPONSE_MESSAGE_TIMEOUT_STATUS_FAILED( + "response-message-timeout-status-failed", + "Response missing, sent Task status is 'failed'" + ), + RESPONSE_MESSAGE_TIMEOUT_STATUS_COMPLETED( + "response-message-timeout-status-completed", + "Response missing, sent Task status is 'completed'" + ), + RESPONSE_MESSAGE_TIMEOUT_STATUS_UNEXPECTED( + "response-message-timeout-status-unexpected", + "Response missing, sent Task status is neither of 'requested', 'in-progress', 'failed' or 'completed'" + ), + + RESPONSE_MESSAGE_TIMEOUT_HTTP_401( + "response-message-timeout-http-401", + "Response message timed out. Received HTTP status 401 trying to check request status on the target" + ), + RESPONSE_MESSAGE_TIMEOUT_HTTP_403( + "response-message-timeout-http-403", + "Response message timed out. Received HTTP status 403 trying to check request status on the target" + ), + RESPONSE_MESSAGE_TIMEOUT_HTTP_500( + "response-message-timeout-http-500", + "Response message timed out. Received HTTP status 500 trying to check request status on the target" + ), + RESPONSE_MESSAGE_TIMEOUT_HTTP_502( + "response-message-timeout-http-502", + "Response message timed out. Received HTTP status 502 trying to check request status on the target" + ), + RESPONSE_MESSAGE_TIMEOUT_HTTP_UNEXPECTED( + "response-message-timeout-http-unexpected", + "Response message timed out. Received an unexpected HTTP status code trying to check request status on the target" + ), + + CLEANUP_MESSAGE_TIMEOUT( + "cleanup-message-timeout", + "Timeout while waiting for cleanup message from remote instance" + ), + + LOCAL_BINARY_DOWNLOAD_IO_ERROR( + "local-binary-download-io-error", + "Local instance encountered an I/O error trying to download the binary resource from the target" + ), + LOCAL_BINARY_DOWNLOAD_HTTP_401( + "local-binary-download-http-401", + "Local instance received HTTP status 401 trying to download the binary resource from the target" + ), + LOCAL_BINARY_DOWNLOAD_HTTP_403( + "local-binary-download-http-403", + "Local instance received HTTP status 403 trying to download the binary resource from the target" + ), + LOCAL_BINARY_DOWNLOAD_HTTP_500( + "local-binary-download-http-500", + "Local instance received HTTP status 500 trying to download the binary resource from the target" + ), + LOCAL_BINARY_DOWNLOAD_HTTP_502( + "local-binary-download-http-502", + "Local instance received HTTP status 500 trying to download the binary resource from the target" + ), + LOCAL_BINARY_DOWNLOAD_HTTP_UNEXPECTED( + "local-binary-download-http-unexpected", + "Local instance received an unexpected HTTP status trying to download the binary resource from the target" + ), + LOCAL_BINARY_DOWNLOAD_TIMEOUT_CONNECT( + "local-binary-download-timeout-connect", + "Local instance encountered a connect timeout trying to download the binary resource from the target" + ), + LOCAL_BINARY_DOWNLOAD_TIMEOUT_READ( + "local-binary-download-timeout-read", + "Local instance encountered a read timeout trying to download the binary resource from the target" + ), + LOCAL_BINARY_DOWNLOAD_HTTP_HOST_CONNECT( + "local-binary-download-http-host-connect", + "Local instance was unable to download the binary resource from the remote DSF FHIR server because the connection was refused" + ), + LOCAL_BINARY_DOWNLOAD_MISSING_REFERENCE( + "local-binary-download-missing-reference", + "Local instance was unable to download the binary resource from the target because the reference was missing" + ), + + REMOTE_BINARY_DOWNLOAD_IO_ERROR( + "remote-binary-download-io-error", + "Remote instance encountered an I/O error trying to download the binary resource from this server" + ), + REMOTE_BINARY_DOWNLOAD_HTTP_401( + "remote-binary-download-http-401", + "Remote instance received HTTP status 401 trying to download the binary resource from this server" + ), + REMOTE_BINARY_DOWNLOAD_HTTP_403( + "remote-binary-download-http-403", + "Remote instance received HTTP status 403 trying to download the binary resource from this server" + ), + REMOTE_BINARY_DOWNLOAD_HTTP_500( + "remote-binary-download-http-500", + "Remote instance received HTTP status 500 trying to download the binary resource from this server" + ), + REMOTE_BINARY_DOWNLOAD_HTTP_502( + "remote-binary-download-http-502", + "Remote instance received HTTP status 502 trying to download the binary resource from this server" + ), + REMOTE_BINARY_DOWNLOAD_HTTP_UNEXPECTED( + "remote-binary-download-http-unexpected", + "Remote instance received an unexpected HTTP status trying to download the binary resource from this server" + ), + REMOTE_BINARY_DOWNLOAD_TIMEOUT_CONNECT( + "remote-binary-download-timeout-connect", + "Remote instance encountered a connect timeout trying to download the binary resource from this server" + ), + REMOTE_BINARY_DOWNLOAD_TIMEOUT_READ( + "remote-binary-download-timeout-read", + "Remote instance encountered a read timeout trying to download the binary resource from this server" + ), + REMOTE_BINARY_DOWNLOAD_HTTP_HOST_CONNECT( + "remote-binary-download-timeout-read", + "Remote instance encountered a read timeout trying to download the binary resource from this server" + ), + REMOTE_BINARY_DOWNLOAD_MISSING_REFERENCE( + "remote-binary-download-http-host-connect", + "Remote instance was unable to download the binary resource from the local DSF FHIR server because the connection was refused" + ), + + LOCAL_UNKNOWN( + "local-unknown", + "An unknown error was encountered by the local instance" + ), + REMOTE_UNKNOWN( + "remote-unknown", + "An unknown error was encountered by the remote instance" + ); + + private final String code; + private final String display; + + private static final Map CODE_TO_ENUM = new HashMap<>(); + + static + { + for (Concept e : values()) + { + CODE_TO_ENUM.put(e.code, e); + } + } + + Concept(String code, String display) + { + this.code = code; + this.display = display; + } + + public String getCode() + { + return code; + } + + public String getDisplay() + { + return display; + } + + public static Concept fromCode(String code) + { + + return CODE_TO_ENUM.get(code); + } + } + } +} diff --git a/src/main/java/dev/dsf/bpe/ConstantsPing.java b/src/main/java/dev/dsf/bpe/ConstantsPing.java index 0f8c12f1..28b54b98 100644 --- a/src/main/java/dev/dsf/bpe/ConstantsPing.java +++ b/src/main/java/dev/dsf/bpe/ConstantsPing.java @@ -1,59 +1,71 @@ package dev.dsf.bpe; +import jakarta.ws.rs.core.MediaType; -public interface ConstantsPing +public final class ConstantsPing { - String PROCESS_NAME_PING_AUTOSTART = "pingAutostart"; - String PROCESS_NAME_PING = "ping"; - String PROCESS_NAME_PONG = "pong"; - - String PROCESS_NAME_FULL_PING_AUTOSTART = "dsfdev_" + PROCESS_NAME_PING_AUTOSTART; - String PROCESS_NAME_FULL_PING = "dsfdev_" + PROCESS_NAME_PING; - String PROCESS_NAME_FULL_PONG = "dsfdev_" + PROCESS_NAME_PONG; - - String PROCESS_DSF_URI_BASE = "http://dsf.dev/bpe/Process/"; - - String PROFILE_DSF_TASK_START_PING_AUTOSTART = "http://dsf.dev/fhir/StructureDefinition/task-start-ping-autostart"; - String PROFILE_DSF_TASK_START_PING_AUTOSTART_PROCESS_URI = PROCESS_DSF_URI_BASE + PROCESS_NAME_PING_AUTOSTART; - String PROFILE_DSF_TASK_START_PING_AUTOSTART_MESSAGE_NAME = "startPingAutostart"; - - String PROFILE_DSF_TASK_STOP_PING_AUTOSTART = "http://dsf.dev/fhir/StructureDefinition/task-stop-ping-autostart"; - String PROFILE_DSF_TASK_STOP_PING_AUTOSTART_PROCESS_URI = PROCESS_DSF_URI_BASE + PROCESS_NAME_PING_AUTOSTART; - String PROFILE_DSF_TASK_STOP_PING_AUTOSTART_MESSAGE_NAME = "stopPingAutostart"; - - String PROFILE_DSF_TASK_START_PING = "http://dsf.dev/fhir/StructureDefinition/task-start-ping"; - String PROFILE_DSF_TASK_START_PING_MESSAGE_NAME = "startPing"; - - String PROFILE_DSF_TASK_PING = "http://dsf.dev/fhir/StructureDefinition/task-ping"; - String PROFILE_DSF_TASK_PING_PROCESS_URI = PROCESS_DSF_URI_BASE + PROCESS_NAME_PING; - String PROFILE_DSF_TASK_PING_MESSAGE_NAME = "ping"; - - String PROFILE_DSF_TASK_PONG_TASK = "http://dsf.dev/fhir/StructureDefinition/task-pong"; - String PROFILE_DSF_TASK_PONG_PROCESS_URI = PROCESS_DSF_URI_BASE + PROCESS_NAME_PONG; - String PROFILE_DSF_TASK_PONG_MESSAGE_NAME = "pong"; - - String CODESYSTEM_DSF_PING = "http://dsf.dev/fhir/CodeSystem/ping"; - String CODESYSTEM_DSF_PING_VALUE_PING_STATUS = "ping-status"; - String CODESYSTEM_DSF_PING_VALUE_PONG_STATUS = "pong-status"; - String CODESYSTEM_DSF_PING_VALUE_ENDPOINT_IDENTIFIER = "endpoint-identifier"; - String CODESYSTEM_DSF_PING_VALUE_TARGET_ENDPOINTS = "target-endpoints"; - String CODESYSTEM_DSF_PING_VALUE_TIMER_INTERVAL = "timer-interval"; - - String CODESYSTEM_DSF_PING_STATUS = "http://dsf.dev/fhir/CodeSystem/ping-status"; - String CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_ALLOWED = "not-allowed"; - String CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_REACHABLE = "not-reachable"; - String CODESYSTEM_DSF_PING_STATUS_VALUE_PONG_MISSING = "pong-missing"; - String CODESYSTEM_DSF_PING_STATUS_VALUE_PONG_RECEIVED = "pong-received"; - String CODESYSTEM_DSF_PING_STATUS_VALUE_PONG_SEND = "pong-send"; - - String EXTENSION_URL_PING_STATUS = "http://dsf.dev/fhir/StructureDefinition/extension-ping-status"; - String EXTENSION_URL_CORRELATION_KEY = "correlation-key"; - String EXTENSION_URL_ORGANIZATION_IDENTIFIER = "organization-identifier"; - String EXTENSION_URL_ENDPOINT_IDENTIFIER = "endpoint-identifier"; - String EXTENSION_URL_ERROR_MESSAGE = "error-message"; - - String BPMN_EXECUTION_VARIABLE_TIMER_INTERVAL = "timerInterval"; - String BPMN_EXECUTION_VARIABLE_STOP_TIMER = "stopTimer"; - - String TIMER_INTERVAL_DEFAULT_VALUE = "PT24H"; + private ConstantsPing() + { + } + + public static final String PROCESS_NAME_PING_AUTOSTART = "pingAutostart"; + public static final String PROCESS_NAME_PING = "ping"; + public static final String PROCESS_NAME_PONG = "pong"; + + public static final String PROCESS_NAME_FULL_PING_AUTOSTART = "dsfdev_" + PROCESS_NAME_PING_AUTOSTART; + public static final String PROCESS_NAME_FULL_PING = "dsfdev_" + PROCESS_NAME_PING; + public static final String PROCESS_NAME_FULL_PONG = "dsfdev_" + PROCESS_NAME_PONG; + + public static final String PROCESS_DSF_URI_BASE = "http://dsf.dev/bpe/Process/"; + + public static final String PROFILE_DSF_TASK_START_PING_AUTOSTART = "http://dsf.dev/fhir/StructureDefinition/task-start-ping-autostart"; + public static final String PROFILE_DSF_TASK_START_PING_AUTOSTART_PROCESS_URI = PROCESS_DSF_URI_BASE + + PROCESS_NAME_PING_AUTOSTART; + public static final String PROFILE_DSF_TASK_START_PING_AUTOSTART_MESSAGE_NAME = "startPingAutostart"; + + public static final String PROFILE_DSF_TASK_STOP_PING_AUTOSTART = "http://dsf.dev/fhir/StructureDefinition/task-stop-ping-autostart"; + public static final String PROFILE_DSF_TASK_STOP_PING_AUTOSTART_PROCESS_URI = PROCESS_DSF_URI_BASE + + PROCESS_NAME_PING_AUTOSTART; + public static final String PROFILE_DSF_TASK_STOP_PING_AUTOSTART_MESSAGE_NAME = "stopPingAutostart"; + + public static final String PROFILE_DSF_TASK_START_PING = "http://dsf.dev/fhir/StructureDefinition/task-start-ping"; + public static final String PROFILE_DSF_TASK_START_PING_MESSAGE_NAME = "startPing"; + + public static final String PROFILE_DSF_TASK_PING = "http://dsf.dev/fhir/StructureDefinition/task-ping"; + public static final String PROFILE_DSF_TASK_PING_PROCESS_URI = PROCESS_DSF_URI_BASE + PROCESS_NAME_PING; + public static final String PROFILE_DSF_TASK_PING_MESSAGE_NAME = "ping"; + + public static final String PROFILE_DSF_TASK_PONG_TASK = "http://dsf.dev/fhir/StructureDefinition/task-pong"; + public static final String PROFILE_DSF_TASK_PONG_PROCESS_URI = PROCESS_DSF_URI_BASE + PROCESS_NAME_PONG; + public static final String PROFILE_DSF_TASK_PONG_MESSAGE_NAME = "pong"; + + public static final String PROFILE_DSF_TASK_CLEANUP_PONG = "http://dsf.dev/fhir/StructureDefinition/task-cleanup-pong"; + public static final String PROFILE_DSF_TASK_CLEANUP_PONG_PROCESS_URI = PROCESS_DSF_URI_BASE + PROCESS_NAME_PONG; + public static final String PROFILE_DSF_TASK_CLEANUP_PONG_MESSAGE_NAME = "cleanupPong"; + + public static final String STRUCTURE_DEFINITION_URL_EXTENSION_PING_STATUS = "http://dsf.dev/fhir/StructureDefinition/extension-ping-status"; + public static final String STRUCTURE_DEFINITION_URL_EXTENSION_ERROR = "http://dsf.dev/fhir/StructureDefinition/extension-error"; + + public static final String EXTENSION_URL_CORRELATION_KEY = "correlation-key"; + public static final String EXTENSION_URL_ORGANIZATION_IDENTIFIER = "organization-identifier"; + public static final String EXTENSION_URL_ENDPOINT_IDENTIFIER = "endpoint-identifier"; + public static final String EXTENSION_URL_DOWNLOAD_SPEED = "download-speed-from-remote"; + public static final String EXTENSION_URL_UPLOAD_SPEED = "upload-speed-to-remote"; + public static final String EXTENSION_URL_ERROR = "error"; + public static final String EXTENSION_URL_ERRORS = "errors"; + public static final String EXTENSION_URL_POTENTIAL_FIX = "potential-fix"; + + public static final long DOWNLOAD_RESOURCE_SIZE_BYTES_DEFAULT = 10000000L; + + public static final MediaType DOWNLOAD_RESOURCE_MIME_TYPE = MediaType.APPLICATION_OCTET_STREAM_TYPE; + + public static final String TIMER_INTERVAL_DEFAULT_VALUE = "PT24H"; + + public static final String POTENTIAL_FIX_URL_BASE = "https://dsf.dev/s"; + public static final String POTENTIAL_FIX_URL_ERROR_HTTP = POTENTIAL_FIX_URL_BASE + "/error-http"; + public static final String POTENTIAL_FIX_URL_READ_TIMEOUT = POTENTIAL_FIX_URL_BASE + "/read-timeout"; + public static final String POTENTIAL_FIX_URL_ERROR_SSL = POTENTIAL_FIX_URL_BASE + "/error-ssl"; + public static final String POTENTIAL_FIX_URL_CONNECTION_TIMEOUT = POTENTIAL_FIX_URL_BASE + "/connection-timeout"; + public static final String POTENTIAL_FIX_URL_CONNECTION_REFUSED = POTENTIAL_FIX_URL_BASE + "/connection-refused"; + public static final String POTENTIAL_FIX_URL_UNKNOWN_HOST = POTENTIAL_FIX_URL_BASE + "/unknown-host"; } diff --git a/src/main/java/dev/dsf/bpe/ExecutionVariables.java b/src/main/java/dev/dsf/bpe/ExecutionVariables.java new file mode 100644 index 00000000..245e15dc --- /dev/null +++ b/src/main/java/dev/dsf/bpe/ExecutionVariables.java @@ -0,0 +1,32 @@ +package dev.dsf.bpe; + +public enum ExecutionVariables +{ + timerInterval, + stopTimer, + downloadResourceSizeBytes, + downloadResource, + downloadResourceReference, + statusCode, + error, + errorLocal, + errorRemote, + errors, + errorsRemote, + downloadedBytes, + downloadedDuration, + targetEndpointIdentifier, + uploadedBytes, + uploadedDuration, + resourceDownloadError, + resourceDownloadErrorRemote, + resourceUploadError, + resourceUploadErrorRemote, + pongTimerDuration, + pingTaskId; + + public String correlatedValue(String correlationKey) + { + return name() + "_" + correlationKey; + } +} diff --git a/src/main/java/dev/dsf/bpe/PingProcessPluginDefinition.java b/src/main/java/dev/dsf/bpe/PingProcessPluginDefinition.java index 12e505f4..e2f1d5c4 100644 --- a/src/main/java/dev/dsf/bpe/PingProcessPluginDefinition.java +++ b/src/main/java/dev/dsf/bpe/PingProcessPluginDefinition.java @@ -10,7 +10,9 @@ public class PingProcessPluginDefinition implements ProcessPluginDefinition { - public static final String VERSION = "1.0.1.0"; + public static final String RESOURCE_VERSION = "2.0"; + public static final String NON_RESOURCE_VERSION = "0.0"; + public static final String VERSION = RESOURCE_VERSION + "." + NON_RESOURCE_VERSION; public static final LocalDate RELEASE_DATE = LocalDate.of(2023, 9, 12); @Override @@ -25,6 +27,12 @@ public String getVersion() return VERSION; } + @Override + public String getResourceVersion() + { + return RESOURCE_VERSION; + } + @Override public LocalDate getReleaseDate() { @@ -52,6 +60,7 @@ public Map> getFhirResourcesByProcessId() var cPing = "fhir/CodeSystem/dsf-ping.xml"; var cPingStatus = "fhir/CodeSystem/dsf-ping-status.xml"; + var cPingError = "fhir/CodeSystem/dsf-ping-error.xml"; var sPingStatus = "fhir/StructureDefinition/dsf-extension-ping-status.xml"; var sPing = "fhir/StructureDefinition/dsf-task-ping.xml"; @@ -59,22 +68,25 @@ public Map> getFhirResourcesByProcessId() var sStartPing = "fhir/StructureDefinition/dsf-task-start-ping.xml"; var sStartPingAutostart = "fhir/StructureDefinition/dsf-task-start-ping-autostart.xml"; var sStopPingAutostart = "fhir/StructureDefinition/dsf-task-stop-ping-autostart.xml"; + var sCleanupPong = "fhir/StructureDefinition/dsf-task-cleanup-pong.xml"; + var sErrorExtension = "fhir/StructureDefinition/dsf-extension-error.xml"; var tStartPing = "fhir/Task/dsf-task-start-ping.xml"; var tStartPingAutoStart = "fhir/Task/dsf-task-start-ping-autostart.xml"; var tStopPingAutoStart = "fhir/Task/dsf-task-stop-ping-autostart.xml"; var vPing = "fhir/ValueSet/dsf-ping.xml"; + var vPingUnits = "fhir/ValueSet/dsf-network-speed-units.xml"; var vPingStatus = "fhir/ValueSet/dsf-ping-status.xml"; var vPongStatus = "fhir/ValueSet/dsf-pong-status.xml"; return Map.of(ConstantsPing.PROCESS_NAME_FULL_PING, - Arrays.asList( - aPing, cPing, cPingStatus, sPingStatus, sStartPing, sPong, tStartPing, vPing, vPingStatus), + Arrays.asList(aPing, cPing, cPingStatus, cPingError, sErrorExtension, sPingStatus, sStartPing, sPong, + sCleanupPong, tStartPing, vPing, vPingStatus, vPingUnits), ConstantsPing.PROCESS_NAME_FULL_PING_AUTOSTART, Arrays.asList(aPingAutostart, cPing, sStartPingAutostart, sStopPingAutostart, tStartPingAutoStart, tStopPingAutoStart, vPing), - ConstantsPing.PROCESS_NAME_FULL_PONG, - Arrays.asList(aPong, cPing, cPingStatus, sPingStatus, sPing, vPing, vPongStatus)); + ConstantsPing.PROCESS_NAME_FULL_PONG, Arrays.asList(aPong, cPing, cPingStatus, cPingError, + sErrorExtension, sPingStatus, sPing, vPing, vPongStatus, vPingUnits)); } } diff --git a/src/main/java/dev/dsf/bpe/ProcessError.java b/src/main/java/dev/dsf/bpe/ProcessError.java new file mode 100644 index 00000000..17a5a8c7 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/ProcessError.java @@ -0,0 +1,74 @@ +package dev.dsf.bpe; + +import java.io.Serializable; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.Task; +import org.hl7.fhir.r4.model.UrlType; + +public record ProcessError(String process, CodeSystem.DsfPingError.Concept concept, String potentialFixUrl) + implements Serializable +{ + public static Extension toExtension(ProcessError error) + { + Objects.requireNonNull(error); + Objects.requireNonNull(error.concept()); + Objects.requireNonNull(error.process()); + + Extension extension = new Extension(); + extension.setUrl(ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_ERROR); + + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ERROR) + .setValue(CodeSystem.DsfPingError.fromConcept(error.concept())); + if (Objects.nonNull(error.potentialFixUrl)) + { + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_POTENTIAL_FIX) + .setValue(new UrlType(error.potentialFixUrl())); + } + + return extension; + } + + public static ProcessError toError(Extension extension, String process) + { + Extension errorExtension = extension.getExtensionByUrl(ConstantsPing.EXTENSION_URL_ERROR); + Objects.requireNonNull(errorExtension); + CodeSystem.DsfPingError.Concept error = CodeSystem.DsfPingError.Concept + .fromCode(((Coding) errorExtension.getValue()).getCode()); + + Extension potentalFixUrlExtension = extension.getExtensionByUrl(ConstantsPing.EXTENSION_URL_POTENTIAL_FIX); + String potentialFixUrl = Objects.nonNull(potentalFixUrlExtension) + ? ((UrlType) potentalFixUrlExtension.getValue()).getValue() + : null; + + return new ProcessError(process, error, potentialFixUrl); + } + + public static List toTaskOutput(List errors) + { + if (errors == null || errors.isEmpty()) + return List.of(); + return errors.stream().map(ProcessError::toTaskOutput).collect(Collectors.toList()); + } + + public static Task.TaskOutputComponent toTaskOutput(ProcessError error) + { + Task.TaskOutputComponent param = new Task.TaskOutputComponent(); + + param.getType().addCoding(CodeSystem.DsfPing.fromCode(CodeSystem.DsfPing.Code.ERROR)); + param.addExtension(ProcessError.toExtension(error)); + Extension dataAbsentReason = new Extension() + .setUrl("http://hl7.org/fhir/StructureDefinition/data-absent-reason") + .setValue(new CodeType("not-applicable")); + param.setValue(new StringType()); + param.getValue().addExtension(dataAbsentReason); + + return param; + } +} diff --git a/src/main/java/dev/dsf/bpe/ProcessErrors.java b/src/main/java/dev/dsf/bpe/ProcessErrors.java new file mode 100644 index 00000000..5c2891d7 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/ProcessErrors.java @@ -0,0 +1,48 @@ +package dev.dsf.bpe; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Vector; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class ProcessErrors +{ + private final List entries; + + public ProcessErrors() + { + entries = new Vector<>(); + } + + @JsonCreator + public ProcessErrors(@JsonProperty("entries") Collection entries) + { + this.entries = new ArrayList<>(entries); + } + + @JsonProperty("entries") + public List getEntries() + { + return entries; + } + + public void add(ProcessError error) + { + entries.add(error); + } + + public void addAll(ProcessErrors errors) + { + entries.addAll(errors.getEntries()); + } + + @JsonIgnore + public boolean isEmpty() + { + return entries.isEmpty(); + } +} diff --git a/src/main/java/dev/dsf/bpe/listener/PingPongProcessPluginDeploymentStateListener.java b/src/main/java/dev/dsf/bpe/listener/PingPongProcessPluginDeploymentStateListener.java new file mode 100644 index 00000000..5bf59b0b --- /dev/null +++ b/src/main/java/dev/dsf/bpe/listener/PingPongProcessPluginDeploymentStateListener.java @@ -0,0 +1,170 @@ +package dev.dsf.bpe.listener; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.CodeSystem; +import org.hl7.fhir.r4.model.MetadataResource; +import org.hl7.fhir.r4.model.Resource; +import org.hl7.fhir.r4.model.StructureDefinition; +import org.springframework.beans.factory.InitializingBean; + +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.PingProcessPluginDefinition; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.ProcessPluginDeploymentStateListener; + +public class PingPongProcessPluginDeploymentStateListener + implements ProcessPluginDeploymentStateListener, InitializingBean +{ + private final ProcessPluginApi api; + + public PingPongProcessPluginDeploymentStateListener(ProcessPluginApi api) + { + this.api = api; + } + + @Override + public void afterPropertiesSet() + { + Objects.requireNonNull(api, "api"); + } + + @Override + public void onProcessesDeployed(List activeProcesses) + { + updateOlderResourcesIfCurrentIsNewestResource(dev.dsf.bpe.CodeSystem.DsfPing.URL, CodeSystem.class, + adaptCodeSystems()); + + updateOlderResourcesIfCurrentIsNewestResource(dev.dsf.bpe.CodeSystem.DsfPingStatus.URL, CodeSystem.class, + adaptCodeSystems()); + + updateOlderResourcesIfCurrentIsNewestResource(dev.dsf.bpe.CodeSystem.DsfPingUnits.URL, CodeSystem.class, + adaptCodeSystems()); + + updateOlderResourcesIfCurrentIsNewestResource(dev.dsf.bpe.CodeSystem.DsfPingError.URL, CodeSystem.class, + adaptCodeSystems()); + + updateOlderResourcesIfCurrentIsNewestResource(ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_PING_STATUS, + StructureDefinition.class, adaptExtensionStructureDefinitions()); + } + + private void updateOlderResourcesIfCurrentIsNewestResource(String url, Class type, + BiConsumer> converter) + { + Bundle searchResult = search(type, url); + List resources = extractAndSortResources(searchResult, type, url); + + if (currentIsNewestResource(resources)) + { + T currentResource = resources.get(resources.size() - 1); + List oldResources = resources.subList(0, resources.size() - 1); + converter.accept(currentResource, oldResources); + } + } + + private Bundle search(Class type, String url) + { + return api.getFhirWebserviceClientProvider().getLocalWebserviceClient().search(type, + Map.of("url", List.of(url))); + } + + private List extractAndSortResources(Bundle bundle, Class type, String url) + { + return bundle.getEntry().stream().filter(Bundle.BundleEntryComponent::hasResource) + .map(Bundle.BundleEntryComponent::getResource).filter(type::isInstance).map(type::cast) + .filter(m -> url.equals(m.getUrl())).sorted((r1, r2) -> + { + MinorMajorVersion version1 = getMajorMinorVersion(r1.getVersion().substring(0, 3)); + MinorMajorVersion version2 = getMajorMinorVersion(r2.getVersion().substring(0, 3)); + + if (version1.major > version2.major) + { + return 1; + } + else if (version1.major < version2.major) + { + return -1; + } + else + { + return Integer.compare(version1.minor, version2.minor); + } + }).toList(); + } + + private boolean currentIsNewestResource(List resources) + { + return !resources.isEmpty() && PingProcessPluginDefinition.RESOURCE_VERSION + .equals(resources.get(resources.size() - 1).getVersion()); + } + + private MinorMajorVersion getMajorMinorVersion(String version) + { + if (version.matches("\\d\\.\\d")) + { + String[] minorMajor = version.split("\\."); + return new MinorMajorVersion(Integer.parseInt(minorMajor[0]), Integer.parseInt(minorMajor[1])); + } + + throw new RuntimeException("Fhir resource version " + version + " does not match regex \\d\\.\\d"); + } + + private BiConsumer> adaptCodeSystems() + { + return (currentResource, olderResources) -> + { + List codeSystemsWithNonMatchingConceptCodes = filterCodeSystemsWithNonMatchingConceptCodesAndAdaptToCurrentCodeSystemConceptCodes( + currentResource, olderResources); + updateResources(codeSystemsWithNonMatchingConceptCodes); + }; + } + + private List filterCodeSystemsWithNonMatchingConceptCodesAndAdaptToCurrentCodeSystemConceptCodes( + CodeSystem currentCodeSystem, List olderCodeSystems) + { + Set currentConceptCodes = getConceptCodes(currentCodeSystem); + return olderCodeSystems.stream().filter(c -> !currentConceptCodes.equals(getConceptCodes(c))) + .map(c -> c.setConcept(currentCodeSystem.getConcept())).toList(); + } + + private Set getConceptCodes(CodeSystem codeSystem) + { + return codeSystem.getConcept().stream().map(CodeSystem.ConceptDefinitionComponent::getCode) + .collect(Collectors.toSet()); + } + + private BiConsumer> adaptExtensionStructureDefinitions() + { + return (currentResource, olderResources) -> + { + overrideDifferentialForOlderExtensionStructureDefinitions(olderResources, + currentResource.getDifferential()); + updateResources(olderResources); + }; + } + + private void overrideDifferentialForOlderExtensionStructureDefinitions( + List olderStructureDefinitions, + StructureDefinition.StructureDefinitionDifferentialComponent newDifferential) + { + olderStructureDefinitions.forEach(olderStructureDefinition -> + { + olderStructureDefinition.setDifferential(newDifferential); + }); + } + + private void updateResources(List resources) + { + resources.forEach(m -> api.getFhirWebserviceClientProvider().getLocalWebserviceClient().update(m)); + } + + private record MinorMajorVersion(int major, int minor) + { + } +} diff --git a/src/main/java/dev/dsf/bpe/listener/SetCorrelationKeyListener.java b/src/main/java/dev/dsf/bpe/listener/SetCorrelationKeyListener.java index 52089288..3672cb3d 100644 --- a/src/main/java/dev/dsf/bpe/listener/SetCorrelationKeyListener.java +++ b/src/main/java/dev/dsf/bpe/listener/SetCorrelationKeyListener.java @@ -4,6 +4,8 @@ import org.camunda.bpm.engine.delegate.DelegateExecution; import org.camunda.bpm.engine.delegate.ExecutionListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; import dev.dsf.bpe.v1.ProcessPluginApi; @@ -13,6 +15,7 @@ public class SetCorrelationKeyListener implements ExecutionListener, InitializingBean { + private static final Logger logger = LoggerFactory.getLogger(SetCorrelationKeyListener.class); private final ProcessPluginApi api; public SetCorrelationKeyListener(ProcessPluginApi api) @@ -29,6 +32,7 @@ public void afterPropertiesSet() throws Exception @Override public void notify(DelegateExecution execution) throws Exception { + logger.debug("Setting correlation key for subprocess instance {}", execution.getProcessInstanceId()); Variables variables = api.getVariables(execution); Target target = variables.getTarget(); diff --git a/src/main/java/dev/dsf/bpe/mail/AggregateErrorMailService.java b/src/main/java/dev/dsf/bpe/mail/AggregateErrorMailService.java new file mode 100644 index 00000000..6d92f0b8 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/mail/AggregateErrorMailService.java @@ -0,0 +1,113 @@ +package dev.dsf.bpe.mail; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.hl7.fhir.r4.model.IdType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.variables.Target; + +public class AggregateErrorMailService implements InitializingBean +{ + private static final Logger errorMailServiceLogger = LoggerFactory.getLogger("error-mail-service-logger"); + private static final String MAIL_MESSAGE_INTRO = "Error(s) while executing ping-pong process:"; + private static final String PING_PROCESS_HAS_ERRORS = "Ping process has errors"; + + private final ProcessPluginApi api; + private final boolean sendProcessFailedMail; + + public AggregateErrorMailService(ProcessPluginApi api, boolean sendProcessFailedMail) + { + this.api = api; + this.sendProcessFailedMail = sendProcessFailedMail; + } + + @Override + public void afterPropertiesSet() + { + Objects.requireNonNull(api, "api"); + } + + public void send(IdType taskId, Map> errorsPerTarget) + { + if (sendProcessFailedMail) + { + api.getMailService().send(PING_PROCESS_HAS_ERRORS, buildMailMessage(taskId, errorsPerTarget)); + errorMailServiceLogger.info("Sent e-mail with process errors"); + } + } + + protected String buildMailMessage(IdType taskId, Map> errorsPerTarget) + { + StringBuilder mailMessage = new StringBuilder(); + + mailMessage.append(MAIL_MESSAGE_INTRO); + mailMessage.append("\n\n"); + + errorsPerTarget.entrySet().stream() + .map(entry -> entry.getValue().stream().map(error -> createMessage(entry.getKey(), error))) + .forEach(messageStream -> messageStream.forEach(message -> + { + mailMessage.append(message); + mailMessage.append("\n\n"); + })); + + mailMessage.append("Process started by: "); + mailMessage.append(taskId.toVersionless() + .withServerBase(api.getEndpointProvider().getLocalEndpointAddress(), "Task").getValue()); + + return mailMessage.toString(); + } + + protected String createMessage(Target target, ProcessError error) + { + StringBuilder b = new StringBuilder(); + + if (error != null && error.process() != null) + { + if (ConstantsPing.PROCESS_NAME_PING.equals(error.process())) + { + b.append(api.getOrganizationProvider().getLocalOrganizationIdentifierValue().orElse("?")); + b.append('/'); + b.append(api.getEndpointProvider().getLocalEndpointIdentifierValue().orElse("?")); + + b.append(" -> "); + + b.append(target.getOrganizationIdentifierValue()); + b.append('/'); + b.append(target.getEndpointIdentifierValue()); + + b.append(":"); + } + else + { + b.append(target.getOrganizationIdentifierValue()); + b.append('/'); + b.append(target.getEndpointIdentifierValue()); + + b.append(" -> "); + + b.append(api.getOrganizationProvider().getLocalOrganizationIdentifierValue().orElse("?")); + b.append('/'); + b.append(api.getEndpointProvider().getLocalEndpointIdentifierValue().orElse("?")); + + b.append(": "); + } + b.append("\n\t"); + b.append("Description: ").append(error.concept().getDisplay()); + } + else + { + b.append("Unable to display error because error is null or process is neither of 'ping' or 'pong'"); + } + + return b.toString(); + } +} diff --git a/src/main/java/dev/dsf/bpe/mail/ErrorMailService.java b/src/main/java/dev/dsf/bpe/mail/ErrorMailService.java deleted file mode 100644 index b3366e01..00000000 --- a/src/main/java/dev/dsf/bpe/mail/ErrorMailService.java +++ /dev/null @@ -1,129 +0,0 @@ -package dev.dsf.bpe.mail; - -import java.util.Objects; - -import org.hl7.fhir.r4.model.IdType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.InitializingBean; - -import dev.dsf.bpe.v1.ProcessPluginApi; -import dev.dsf.bpe.v1.variables.Target; - -public class ErrorMailService implements InitializingBean -{ - private static final Logger pingProcessErrorLogger = LoggerFactory.getLogger("ping-process-error-logger"); - private static final Logger pongProcessErrorLogger = LoggerFactory.getLogger("pong-process-error-logger"); - - private static final String SUBJECT_PING_PROCESS_FAILED = "Ping Process Failed"; - private static final String SUBJECT_PONG_PROCESS_FAILED = "Pong Process Failed"; - - private final ProcessPluginApi api; - - private final boolean sendPingProcessFailedMail; - private final boolean sendPongProcessFailedMail; - - public ErrorMailService(ProcessPluginApi api, boolean sendPingProcessFailedMail, boolean sendPongProcessFailedMail) - { - this.api = api; - - this.sendPingProcessFailedMail = sendPingProcessFailedMail; - this.sendPongProcessFailedMail = sendPongProcessFailedMail; - } - - @Override - public void afterPropertiesSet() throws Exception - { - Objects.requireNonNull(api, "api"); - } - - private String createMessage(Target target, String message, String messageDetails, IdType taskId) - { - StringBuilder b = new StringBuilder(); - - b.append(api.getOrganizationProvider().getLocalOrganizationIdentifierValue().orElse("?")); - b.append('/'); - b.append(api.getEndpointProvider().getLocalEndpointIdentifierValue().orElse("?")); - - b.append(" -> "); - - b.append(target.getOrganizationIdentifierValue()); - b.append('/'); - b.append(target.getEndpointIdentifierValue()); - - b.append(": "); - b.append(message); - - if (messageDetails != null) - { - b.append("\n\t"); - b.append(messageDetails); - } - - b.append("\n\nProcess started by: "); - b.append(taskId.toVersionless().withServerBase(api.getEndpointProvider().getLocalEndpointAddress(), "Task") - .getValue()); - - return b.toString(); - } - - public void pongMessageNotReceived(IdType taskId, Target target) - { - pingProcessErrorLogger.debug("No pong from organization '{}', Endpoint '{}' received", - target.getOrganizationIdentifierValue(), target.getEndpointIdentifierValue()); - - if (sendPingProcessFailedMail) - { - api.getMailService().send(SUBJECT_PING_PROCESS_FAILED, - createMessage(target, "No pong message received", null, taskId)); - } - } - - public void endpointNotReachableForPing(IdType taskId, Target target, String errorMessage) - { - pingProcessErrorLogger.debug("Endpoint '{}' at organization '{}' not reachable with ping: {}", - target.getOrganizationIdentifierValue(), target.getEndpointIdentifierValue(), errorMessage); - - if (sendPingProcessFailedMail) - { - api.getMailService().send(SUBJECT_PING_PROCESS_FAILED, - createMessage(target, "Not reachable with ping", errorMessage, taskId)); - } - } - - public void endpointReachablePingForbidden(IdType taskId, Target target, String errorMessage) - { - pingProcessErrorLogger.debug("Endpoint '{}' at organization '{}' reachable, ping forbidden: {}", - target.getOrganizationIdentifierValue(), target.getEndpointIdentifierValue(), errorMessage); - - if (sendPongProcessFailedMail) - { - api.getMailService().send(SUBJECT_PING_PROCESS_FAILED, - createMessage(target, "Ping forbidden", errorMessage, taskId)); - } - } - - public void endpointNotReachableForPong(IdType taskId, Target target, String errorMessage) - { - pongProcessErrorLogger.debug("Endpoint '{}' at organization '{}' not reachable with pong: {}", - target.getOrganizationIdentifierValue(), target.getEndpointIdentifierValue(), errorMessage); - - if (sendPongProcessFailedMail) - { - api.getMailService().send(SUBJECT_PONG_PROCESS_FAILED, - createMessage(target, "Not reachable with pong", errorMessage, taskId)); - } - } - - public void endpointReachablePongForbidden(IdType taskId, Target target, String errorMessage) - { - pongProcessErrorLogger.debug("Endpoint '{}' at organization '{}' reachable, pong forbidden: {}", - target.getOrganizationIdentifierValue(), target.getEndpointIdentifierValue(), errorMessage); - - if (sendPongProcessFailedMail) - { - api.getMailService().send(SUBJECT_PONG_PROCESS_FAILED, - createMessage(target, "Pong forbidden", errorMessage, taskId)); - } - } -} diff --git a/src/main/java/dev/dsf/bpe/message/CleanupPongMessage.java b/src/main/java/dev/dsf/bpe/message/CleanupPongMessage.java new file mode 100644 index 00000000..eb148c12 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/message/CleanupPongMessage.java @@ -0,0 +1,102 @@ +package dev.dsf.bpe.message; + +import java.time.Duration; +import java.util.stream.Stream; + +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.util.task.SendTaskErrorConverter; +import dev.dsf.bpe.util.task.input.generator.DownloadedBytesGenerator; +import dev.dsf.bpe.util.task.input.generator.DownloadedDurationGenerator; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractTaskMessageSend; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; +import dev.dsf.bpe.variables.process_error.ProcessErrorValueImpl; + +public class CleanupPongMessage extends AbstractTaskMessageSend +{ + private static final Logger logger = LoggerFactory.getLogger(CleanupPongMessage.class); + + public CleanupPongMessage(ProcessPluginApi api) + { + super(api); + } + + @Override + protected Stream getAdditionalInputParameters(DelegateExecution execution, + Variables variables) + { + Target target = variables.getTarget(); + String correlationKey = target.getCorrelationKey(); + Long downloadedBytes = variables.getLong(ExecutionVariables.downloadedBytes.correlatedValue(correlationKey)); + Duration downloadedDuration = (Duration) variables + .getVariable(ExecutionVariables.downloadedDuration.correlatedValue(correlationKey)); + + Stream downloadedBytesParameter = downloadedBytes != null + ? Stream.of(DownloadedBytesGenerator.create(downloadedBytes)) + : Stream.empty(); + Stream downloadedDurationParameter = downloadedDuration != null + ? Stream.of(DownloadedDurationGenerator.create(downloadedDuration)) + : Stream.empty(); + + return Stream.of(downloadedBytesParameter, downloadedDurationParameter).flatMap(s -> s); + } + + @Override + protected void handleSendTaskError(DelegateExecution execution, Variables variables, Exception exception, + String errorMessage) + { + Target target = variables.getTarget(); + SendTaskErrorConverter.ProcessErrorWithStatusCode errorAndStatus = SendTaskErrorConverter + .convertLocal(exception, false, ConstantsPing.PROCESS_NAME_PING); + + execution.setVariableLocal(ExecutionVariables.error.name(), new ProcessErrorValueImpl(errorAndStatus.error())); + execution.setVariableLocal(ExecutionVariables.statusCode.name(), CodeSystem.DsfPing.Code.ERROR.getValue()); + + logger.info("Request to {} resulted in error: {}", target.getEndpointUrl(), + errorAndStatus.error().concept().getDisplay()); + } + + @Override + protected void sendTask(DelegateExecution execution, Variables variables, Target target, + String instantiatesCanonical, String messageName, String businessKey, String profile, + Stream additionalInputParameters) + { + Target newTarget = new Target() + { + @Override + public String getOrganizationIdentifierValue() + { + return target.getOrganizationIdentifierValue(); + } + + @Override + public String getEndpointIdentifierValue() + { + return target.getEndpointIdentifierValue(); + } + + @Override + public String getEndpointUrl() + { + return target.getEndpointUrl(); + } + + @Override + public String getCorrelationKey() + { + return null; + } + }; + + super.sendTask(execution, variables, newTarget, instantiatesCanonical, messageName, businessKey, profile, + additionalInputParameters); + } +} diff --git a/src/main/java/dev/dsf/bpe/message/SendPing.java b/src/main/java/dev/dsf/bpe/message/SendPing.java deleted file mode 100644 index 421fc176..00000000 --- a/src/main/java/dev/dsf/bpe/message/SendPing.java +++ /dev/null @@ -1,72 +0,0 @@ -package dev.dsf.bpe.message; - -import java.util.stream.Stream; - -import org.camunda.bpm.engine.delegate.DelegateExecution; -import org.hl7.fhir.r4.model.Identifier; -import org.hl7.fhir.r4.model.Reference; -import org.hl7.fhir.r4.model.ResourceType; -import org.hl7.fhir.r4.model.Task; -import org.hl7.fhir.r4.model.Task.ParameterComponent; - -import dev.dsf.bpe.ConstantsPing; -import dev.dsf.bpe.v1.ProcessPluginApi; -import dev.dsf.bpe.v1.activity.AbstractTaskMessageSend; -import dev.dsf.bpe.v1.variables.Variables; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.StatusType; - -public class SendPing extends AbstractTaskMessageSend -{ - public SendPing(ProcessPluginApi api) - { - super(api); - } - - @Override - protected Stream getAdditionalInputParameters(DelegateExecution execution, Variables variables) - { - return Stream.of(api.getTaskHelper().createInput( - new Reference().setIdentifier(getLocalEndpointIdentifier()).setType(ResourceType.Endpoint.name()), - ConstantsPing.CODESYSTEM_DSF_PING, ConstantsPing.CODESYSTEM_DSF_PING_VALUE_ENDPOINT_IDENTIFIER)); - } - - @Override - protected void handleIntermediateThrowEventError(DelegateExecution execution, Variables variables, - Exception exception, String errorMessage) - { - String statusCode = exception instanceof WebApplicationException w && w.getResponse() != null - && w.getResponse().getStatus() == Response.Status.FORBIDDEN.getStatusCode() - ? ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_ALLOWED - : ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_REACHABLE; - execution.setVariableLocal("statusCode", statusCode); - - String specialErrorMessage = createErrorMessage(exception); - execution.setVariableLocal("errorMessage", specialErrorMessage); - } - - @Override - protected void addErrorMessage(Task task, String errorMessage) - { - // error message part of - } - - private String createErrorMessage(Exception exception) - { - if (exception instanceof WebApplicationException w - && (exception.getMessage() == null || exception.getMessage().isBlank())) - { - StatusType statusInfo = w.getResponse().getStatusInfo(); - return statusInfo.getStatusCode() + " " + statusInfo.getReasonPhrase(); - } - else - return exception.getMessage(); - } - - private Identifier getLocalEndpointIdentifier() - { - return api.getEndpointProvider().getLocalEndpointIdentifier() - .orElseThrow(() -> new IllegalStateException("Local endpoint identifier unknown")); - } -} diff --git a/src/main/java/dev/dsf/bpe/message/SendPingMessage.java b/src/main/java/dev/dsf/bpe/message/SendPingMessage.java new file mode 100644 index 00000000..df30bce4 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/message/SendPingMessage.java @@ -0,0 +1,108 @@ +package dev.dsf.bpe.message; + +import java.util.stream.Stream; + +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.ResourceType; +import org.hl7.fhir.r4.model.Task; +import org.hl7.fhir.r4.model.Task.ParameterComponent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.PingProcessPluginDefinition; +import dev.dsf.bpe.util.task.SendTaskErrorConverter; +import dev.dsf.bpe.util.task.input.generator.DownloadResourceReferenceGenerator; +import dev.dsf.bpe.util.task.input.generator.DownloadResourceSizeGenerator; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractTaskMessageSend; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; +import dev.dsf.bpe.variables.codesystem.dsfpingstatus.CodeValueImpl; +import dev.dsf.bpe.variables.process_error.ProcessErrorValueImpl; +import dev.dsf.fhir.client.FhirWebserviceClient; + +public class SendPingMessage extends AbstractTaskMessageSend +{ + private static final Logger logger = LoggerFactory.getLogger(SendPingMessage.class); + private IdType taskId; + + public SendPingMessage(ProcessPluginApi api) + { + super(api); + } + + @Override + protected Stream getAdditionalInputParameters(DelegateExecution execution, Variables variables) + { + String downloadResourceReference = variables.getString(ExecutionVariables.downloadResourceReference.name()); + long downloadResourceSizeBytes = variables.getLong(ExecutionVariables.downloadResourceSizeBytes.name()); + + Stream downloadResourceReferenceStream = downloadResourceReference == null ? Stream.empty() + : Stream.of(DownloadResourceReferenceGenerator.create(downloadResourceReference)); + Stream downloadResourceSizeBytesStream = Stream + .of(DownloadResourceSizeGenerator.create(downloadResourceSizeBytes)); + ParameterComponent endpointIdentifierComponent = api.getTaskHelper().createInput( + new Reference().setIdentifier(getLocalEndpointIdentifier()).setType(ResourceType.Endpoint.name()), + CodeSystem.DsfPing.URL, CodeSystem.DsfPing.Code.ENDPOINT_IDENTIFIER.getValue()); + endpointIdentifierComponent.getType().getCodingFirstRep() + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); + Stream endpointIdentifierStream = Stream.of(endpointIdentifierComponent); + + return Stream.concat(endpointIdentifierStream, + Stream.concat(downloadResourceReferenceStream, downloadResourceSizeBytesStream)); + } + + @Override + protected void sendTask(DelegateExecution execution, Variables variables, Target target, + String instantiatesCanonical, String messageName, String businessKey, String profile, + Stream additionalInputParameters) + { + super.sendTask(execution, variables, target, instantiatesCanonical, messageName, businessKey, profile, + additionalInputParameters); + if (taskId != null) + { + execution.setVariableLocal(ExecutionVariables.pingTaskId.name(), taskId.getIdPart()); + } + } + + @Override + protected IdType doSend(FhirWebserviceClient client, Task task) + { + taskId = super.doSend(client, task); + return taskId; + } + + @Override + protected void handleSendTaskError(DelegateExecution execution, Variables variables, Exception exception, + String errorMessage) + { + Target target = variables.getTarget(); + SendTaskErrorConverter.ProcessErrorWithStatusCode errorAndStatus = SendTaskErrorConverter + .convertLocal(exception, true, ConstantsPing.PROCESS_NAME_PING); + + execution.setVariableLocal(ExecutionVariables.error.name(), new ProcessErrorValueImpl(errorAndStatus.error())); + execution.setVariableLocal(ExecutionVariables.statusCode.name(), + new CodeValueImpl(errorAndStatus.statusCode())); + + logger.info("Request to {} resulted in error: {}", target.getEndpointUrl(), + errorAndStatus.error().concept().getDisplay()); + } + + @Override + protected void addErrorMessage(Task task, String errorMessage) + { + // error message part of status extension + } + + private Identifier getLocalEndpointIdentifier() + { + return api.getEndpointProvider().getLocalEndpointIdentifier() + .orElseThrow(() -> new IllegalStateException("Local endpoint identifier unknown")); + } +} diff --git a/src/main/java/dev/dsf/bpe/message/SendPong.java b/src/main/java/dev/dsf/bpe/message/SendPong.java deleted file mode 100644 index 123a3441..00000000 --- a/src/main/java/dev/dsf/bpe/message/SendPong.java +++ /dev/null @@ -1,92 +0,0 @@ -package dev.dsf.bpe.message; - -import java.util.Objects; - -import org.camunda.bpm.engine.delegate.DelegateExecution; -import org.hl7.fhir.r4.model.Task; - -import dev.dsf.bpe.ConstantsPing; -import dev.dsf.bpe.mail.ErrorMailService; -import dev.dsf.bpe.util.PingStatusGenerator; -import dev.dsf.bpe.v1.ProcessPluginApi; -import dev.dsf.bpe.v1.activity.AbstractTaskMessageSend; -import dev.dsf.bpe.v1.variables.Target; -import dev.dsf.bpe.v1.variables.Variables; -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.Response.StatusType; - -public class SendPong extends AbstractTaskMessageSend -{ - private final PingStatusGenerator statusGenerator; - private final ErrorMailService errorMailService; - - public SendPong(ProcessPluginApi api, PingStatusGenerator statusGenerator, ErrorMailService errorMailService) - { - super(api); - - this.statusGenerator = statusGenerator; - this.errorMailService = errorMailService; - } - - @Override - public void afterPropertiesSet() throws Exception - { - super.afterPropertiesSet(); - - Objects.requireNonNull(statusGenerator, "statusGenerator"); - Objects.requireNonNull(errorMailService, "errorMailService"); - } - - @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws Exception - { - super.doExecute(execution, variables); - - Target target = variables.getTarget(); - Task mainTask = variables.getStartTask(); - mainTask.addOutput(statusGenerator.createPongStatusOutput(target, - ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_PONG_SEND)); - variables.updateTask(mainTask); - } - - @Override - protected void handleEndEventError(DelegateExecution execution, Variables variables, Exception exception, - String errorMessage) - { - Target target = variables.getTarget(); - Task mainTask = variables.getStartTask(); - - if (mainTask != null) - { - String statusCode = exception instanceof WebApplicationException w && w.getResponse() != null - && w.getResponse().getStatus() == Response.Status.FORBIDDEN.getStatusCode() - ? ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_ALLOWED - : ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_REACHABLE; - - String specialErrorMessage = createErrorMessage(exception); - - mainTask.addOutput(statusGenerator.createPongStatusOutput(target, statusCode, specialErrorMessage)); - variables.updateTask(mainTask); - - if (ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_REACHABLE.equals(statusCode)) - errorMailService.endpointNotReachableForPong(mainTask.getIdElement(), target, specialErrorMessage); - else if (ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_ALLOWED.equals(statusCode)) - errorMailService.endpointReachablePongForbidden(mainTask.getIdElement(), target, specialErrorMessage); - } - - super.handleEndEventError(execution, variables, exception, errorMessage); - } - - private String createErrorMessage(Exception exception) - { - if (exception instanceof WebApplicationException w - && (exception.getMessage() == null || exception.getMessage().isBlank())) - { - StatusType statusInfo = w.getResponse().getStatusInfo(); - return statusInfo.getStatusCode() + " " + statusInfo.getReasonPhrase(); - } - else - return exception.getMessage(); - } -} diff --git a/src/main/java/dev/dsf/bpe/message/SendPongMessage.java b/src/main/java/dev/dsf/bpe/message/SendPongMessage.java new file mode 100644 index 00000000..c5c34497 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/message/SendPongMessage.java @@ -0,0 +1,114 @@ +package dev.dsf.bpe.message; + +import java.time.Duration; +import java.util.Objects; +import java.util.stream.Stream; + +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessErrors; +import dev.dsf.bpe.mail.AggregateErrorMailService; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.util.task.SendTaskErrorConverter; +import dev.dsf.bpe.util.task.input.generator.DownloadResourceReferenceGenerator; +import dev.dsf.bpe.util.task.input.generator.DownloadedBytesGenerator; +import dev.dsf.bpe.util.task.input.generator.DownloadedDurationGenerator; +import dev.dsf.bpe.util.task.input.generator.ErrorInputComponentGenerator; +import dev.dsf.bpe.util.task.output.generator.PingStatusGenerator; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractTaskMessageSend; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; +import dev.dsf.bpe.variables.codesystem.dsfpingstatus.CodeValueImpl; + +public class SendPongMessage extends AbstractTaskMessageSend +{ + private static final Logger logger = LoggerFactory.getLogger(SendPongMessage.class); + + private final AggregateErrorMailService errorMailService; + + public SendPongMessage(ProcessPluginApi api, AggregateErrorMailService errorMailService) + { + super(api); + + this.errorMailService = errorMailService; + } + + @Override + public void afterPropertiesSet() throws Exception + { + super.afterPropertiesSet(); + + Objects.requireNonNull(errorMailService, "errorMailService"); + } + + @Override + protected Stream getAdditionalInputParameters(DelegateExecution execution, + Variables variables) + { + ProcessErrors errorListRemote = ErrorListUtils.getErrorListRemote(execution); + long downloadResourceSizeBytes = variables.getLong(ExecutionVariables.downloadResourceSizeBytes.name()); + if (downloadResourceSizeBytes >= 0) + { + Long downloadedBytes = variables.getLong(ExecutionVariables.downloadedBytes.name()); + Duration downloadedDuration = (Duration) variables + .getVariable(ExecutionVariables.downloadedDuration.name()); + String downloadResourceReference = variables.getString(ExecutionVariables.downloadResourceReference.name()); + + Stream downloadedBytesParameter = downloadedBytes != null + ? Stream.of(DownloadedBytesGenerator.create(downloadedBytes)) + : Stream.empty(); + Stream downloadedDurationParameter = downloadedDuration != null + ? Stream.of(DownloadedDurationGenerator.create(downloadedDuration)) + : Stream.empty(); + Stream downloadedResourceReferenceParameter = downloadResourceReference != null + ? Stream.of(DownloadResourceReferenceGenerator.create(downloadResourceReference)) + : Stream.empty(); + + return Stream + .of(downloadedBytesParameter, downloadedDurationParameter, downloadedResourceReferenceParameter, + ErrorInputComponentGenerator.create(errorListRemote.getEntries()).stream()) + .flatMap(stream -> stream); + } + else + { + return ErrorInputComponentGenerator.create(errorListRemote.getEntries()).stream(); + } + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws Exception + { + Target target = variables.getTarget(); + Task mainTask = variables.getStartTask(); + variables.setVariable(ExecutionVariables.statusCode.name(), + new CodeValueImpl(CodeSystem.DsfPingStatus.Code.PONG_SENT)); + PingStatusGenerator.updatePongStatusOutput(mainTask, target); + variables.updateTask(mainTask); + super.doExecute(execution, variables); + } + + @Override + protected void handleSendTaskError(DelegateExecution execution, Variables variables, Exception exception, + String errorMessage) + { + Target target = variables.getTarget(); + Task startTask = variables.getStartTask(); + + SendTaskErrorConverter.ProcessErrorWithStatusCode errorAndStatus = SendTaskErrorConverter + .convertLocal(exception, true, ConstantsPing.PROCESS_NAME_PONG); + + ErrorListUtils.add(errorAndStatus.error(), execution); + variables.setVariable(ExecutionVariables.statusCode.name(), new CodeValueImpl(errorAndStatus.statusCode())); + variables.updateTask(startTask); + + logger.info("Request to {} resulted in error: {}", target.getEndpointUrl(), + errorAndStatus.error().concept().getDisplay()); + } +} diff --git a/src/main/java/dev/dsf/bpe/message/SendStartPing.java b/src/main/java/dev/dsf/bpe/message/SendStartPing.java index 196d828b..42772b92 100644 --- a/src/main/java/dev/dsf/bpe/message/SendStartPing.java +++ b/src/main/java/dev/dsf/bpe/message/SendStartPing.java @@ -1,14 +1,16 @@ package dev.dsf.bpe.message; +import java.util.UUID; import java.util.stream.Stream; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.hl7.fhir.r4.model.Task; import org.hl7.fhir.r4.model.Task.ParameterComponent; -import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.CodeSystem; import dev.dsf.bpe.v1.ProcessPluginApi; import dev.dsf.bpe.v1.activity.AbstractTaskMessageSend; +import dev.dsf.bpe.v1.variables.Target; import dev.dsf.bpe.v1.variables.Variables; public class SendStartPing extends AbstractTaskMessageSend @@ -21,9 +23,36 @@ public SendStartPing(ProcessPluginApi api) @Override protected Stream getAdditionalInputParameters(DelegateExecution execution, Variables variables) { - return variables.getStartTask().getInput().stream().filter(Task.ParameterComponent::hasType) - .filter(i -> i.getType().getCoding().stream() - .anyMatch(c -> ConstantsPing.CODESYSTEM_DSF_PING.equals(c.getSystem()) - && ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TARGET_ENDPOINTS.equals(c.getCode()))); + return Stream.concat( + variables.getStartTask().getInput().stream().filter(Task.ParameterComponent::hasType) + .filter(i -> i.getType().getCoding().stream() + .anyMatch(c -> CodeSystem.DsfPing.URL.equals(c.getSystem()) + && CodeSystem.DsfPing.Code.TARGET_ENDPOINTS.getValue().equals(c.getCode()))), + Stream.of(getDownloadResourceSizeInputParameter(variables))); + } + + private ParameterComponent getDownloadResourceSizeInputParameter(Variables variables) + { + return variables.getStartTask().getInput().stream().filter(this::isDownloadResourceSizeParameter).findFirst() + .orElseThrow(); + } + + private boolean isDownloadResourceSizeParameter(ParameterComponent parameterComponent) + { + return parameterComponent.getType().getCoding().stream() + .anyMatch(t -> CodeSystem.DsfPing.URL.equals(t.getSystem()) + && CodeSystem.DsfPing.Code.DOWNLOAD_RESOURCE_SIZE_BYTES.getValue().equals(t.getCode())); + } + + @Override + protected void sendTask(DelegateExecution execution, Variables variables, Target target, + String instantiatesCanonical, String messageName, String businessKey, String profile, + Stream additionalInputParameters) + { + // different business-key for every start-ping execution + businessKey = UUID.randomUUID().toString(); + + super.sendTask(execution, variables, target, instantiatesCanonical, messageName, businessKey, profile, + additionalInputParameters); } } diff --git a/src/main/java/dev/dsf/bpe/service/Cleanup.java b/src/main/java/dev/dsf/bpe/service/Cleanup.java new file mode 100644 index 00000000..b9c64990 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/Cleanup.java @@ -0,0 +1,154 @@ +package dev.dsf.bpe.service; + +import java.net.SocketTimeoutException; +import java.util.Locale; + +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.HttpHostConnectException; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.Expression; +import org.hl7.fhir.r4.model.Binary; +import org.hl7.fhir.r4.model.IdType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.WebApplicationException; + +public class Cleanup extends AbstractServiceDelegate implements InitializingBean +{ + private static final Logger logger = LoggerFactory.getLogger(Cleanup.class); + private Expression process; + + public Cleanup(ProcessPluginApi api) + { + super(api); + } + + public void doExecute(DelegateExecution delegateExecution, Variables variables) + { + logger.debug("Cleaning up..."); + + String process = (String) this.process.getValue(delegateExecution); + + String downloadResourceId = new IdType(variables.getString(ExecutionVariables.downloadResourceReference.name())) + .getIdPart(); + if (downloadResourceId != null) + { + try + { + api.getFhirWebserviceClientProvider().getLocalWebserviceClient().delete(Binary.class, + downloadResourceId); + api.getFhirWebserviceClientProvider().getLocalWebserviceClient().deletePermanently(Binary.class, + downloadResourceId); + logger.debug("Deleted Binary resource with ID {}", downloadResourceId); + } + catch (ProcessingException e) + { + if (e.getCause() instanceof SocketTimeoutException timeoutException) + { + ProcessError error = toProcessError(timeoutException, process); + ErrorListUtils.add(error, delegateExecution); + logger.error(e.getCause().getMessage()); + } + else if (e.getCause() instanceof ConnectTimeoutException) + { + ProcessError error = toProcessErrorConnectTimeout(process); + ErrorListUtils.add(error, delegateExecution); + logger.error(e.getCause().getMessage()); + } + else if (e.getCause() instanceof HttpHostConnectException) + { + ProcessError error = toProcessErrorLocalHttpHostConnect(process); + ErrorListUtils.add(error, delegateExecution); + logger.error(e.getCause().getMessage()); + } + else + { + ProcessError error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, null); + ErrorListUtils.add(error, delegateExecution); + logger.error("Unexpected error: {}", e.getCause().getMessage()); + } + } + catch (WebApplicationException e) + { + ProcessError error = toProcessError(e, process); + ErrorListUtils.add(error, delegateExecution); + } + } + else + { + logger.debug("Nothing to do"); + } + logger.debug("Cleanup complete."); + } + + private ProcessError toProcessError(SocketTimeoutException timeoutException, String process) + { + ProcessError error; + String message = timeoutException.getMessage().toLowerCase(Locale.ROOT); + if (message.contains("connect")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DELETE_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + else if (message.contains("read")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DELETE_TIMEOUT_READ, + ConstantsPing.POTENTIAL_FIX_URL_READ_TIMEOUT); + } + else + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, null); + logger.error("Unexpected error: {}", timeoutException.getMessage()); + } + return error; + } + + private ProcessError toProcessErrorLocalHttpHostConnect(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DELETE_HTTP_HOST_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_REFUSED); + } + + private ProcessError toProcessErrorConnectTimeout(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DELETE_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + + private ProcessError toProcessError(WebApplicationException e, String process) + { + int status = e.getResponse().getStatus(); + String message = "Response from local DSF FHIR server: " + status; + logger.error(message, e); + + return switch (status) + { + case 401 -> new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DELETE_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + case 403 -> new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DELETE_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + case 500 -> new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DELETE_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + case 502 -> new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DELETE_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + default -> new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DELETE_HTTP_UNEXPECTED, + null); + }; + } + + public void setProcess(Expression process) + { + this.process = process; + } +} diff --git a/src/main/java/dev/dsf/bpe/service/GenerateAndStoreResource.java b/src/main/java/dev/dsf/bpe/service/GenerateAndStoreResource.java new file mode 100644 index 00000000..406cafb3 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/GenerateAndStoreResource.java @@ -0,0 +1,274 @@ +package dev.dsf.bpe.service; + +import java.net.SocketTimeoutException; +import java.util.Locale; + +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.HttpHostConnectException; +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.camunda.bpm.engine.delegate.Expression; +import org.hl7.fhir.r4.model.IdType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; +import dev.dsf.bpe.variables.process_error.ProcessErrorValueImpl; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.WebApplicationException; + +public class GenerateAndStoreResource extends AbstractServiceDelegate implements InitializingBean +{ + private static final Logger logger = LoggerFactory.getLogger(GenerateAndStoreResource.class); + private final long maxUploadSizeBytes; + private Expression process; + + public GenerateAndStoreResource(ProcessPluginApi api, long maxUploadSizeBytes) + { + super(api); + this.maxUploadSizeBytes = maxUploadSizeBytes; + } + + @Override + public void afterPropertiesSet() throws Exception + { + super.afterPropertiesSet(); + } + + public void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + logger.debug("Generating resource..."); + long downloadResourceSizeBytes = getDownloadResourceSize(variables); + String process = (String) this.process.getValue(delegateExecution); + RandomByteInputStream resourceContent; + if (downloadResourceSizeBytes > maxUploadSizeBytes) + { + logger.info( + "Requested resource size of {} bytes exceeds configured maximum upload size of {} bytes. Trimmed to maximum upload size.", + downloadResourceSizeBytes, maxUploadSizeBytes); + setDownloadResourceSizeBytes(variables, maxUploadSizeBytes); + resourceContent = new RandomByteInputStream(maxUploadSizeBytes); + } + else + { + setDownloadResourceSizeBytes(variables, downloadResourceSizeBytes); + resourceContent = new RandomByteInputStream(downloadResourceSizeBytes); + } + logger.debug("Generated resource."); + logger.debug("Storing binary resource for download..."); + + try + { + IdType downloadResource = storeBinary(resourceContent); + + String reference = downloadResource.toVersionless().getValueAsString(); + + variables.setString(ExecutionVariables.downloadResourceReference.name(), reference); + + logger.debug("Stored binary resource for download"); + } + catch (WebApplicationException e) + { + int status = e.getResponse().getStatus(); + ProcessError error; + ProcessError errorRemote; + + switch (status) + { + case 401: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_POST_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + case 403: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_POST_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + case 413: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_HTTP_413, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_POST_HTTP_413, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + case 500: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_POST_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + case 502: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_POST_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + default: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_HTTP_UNEXPECTED, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_POST_HTTP_UNEXPECTED, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + } + + variables.setVariable(ExecutionVariables.resourceUploadError.name(), new ProcessErrorValueImpl(error)); + if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + variables.setVariable(ExecutionVariables.resourceUploadErrorRemote.name(), + new ProcessErrorValueImpl(errorRemote)); + } + } + catch (ProcessingException e) + { + if (e.getCause() instanceof SocketTimeoutException socketTimeoutException) + { + ProcessError error = toProcessErrorLocal(socketTimeoutException, process); + ProcessError errorRemote = toProcessErrorRemote(socketTimeoutException, process); + + variables.setVariable(ExecutionVariables.resourceUploadError.name(), new ProcessErrorValueImpl(error)); + if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + variables.setVariable(ExecutionVariables.resourceUploadErrorRemote.name(), + new ProcessErrorValueImpl(errorRemote)); + } + } + else if (e.getCause() instanceof ConnectTimeoutException) + { + ProcessError error = toProcessErrorLocalConnectTimeout(process); + ProcessError errorRemote = toProcessErrorRemoteConnectTimeout(process); + + variables.setVariable(ExecutionVariables.resourceUploadError.name(), new ProcessErrorValueImpl(error)); + if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + variables.setVariable(ExecutionVariables.resourceUploadErrorRemote.name(), + new ProcessErrorValueImpl(errorRemote)); + } + } + else if (e.getCause() instanceof HttpHostConnectException) + { + ProcessError error = toProcessErrorLocalHttpHostConnect(process); + ProcessError errorRemote = toProcessErrorRemoteHttpHostConnect(process); + + variables.setVariable(ExecutionVariables.resourceUploadError.name(), new ProcessErrorValueImpl(error)); + if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + variables.setVariable(ExecutionVariables.resourceUploadErrorRemote.name(), + new ProcessErrorValueImpl(errorRemote)); + } + } + else + { + ProcessError error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, null); + ProcessError errorRemote = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_UNKNOWN, + null); + variables.setVariable(ExecutionVariables.resourceUploadError.name(), new ProcessErrorValueImpl(error)); + if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + variables.setVariable(ExecutionVariables.resourceUploadErrorRemote.name(), + new ProcessErrorValueImpl(errorRemote)); + } + logger.error("Unexpected error: {}", e.getMessage()); + } + } + } + + private ProcessError toProcessErrorLocal(SocketTimeoutException timeoutException, String process) + { + ProcessError error; + String message = timeoutException.getMessage().toLowerCase(Locale.ROOT); + if (message.contains("connect")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + else if (message.contains("read")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_TIMEOUT_READ, + ConstantsPing.POTENTIAL_FIX_URL_READ_TIMEOUT); + } + else + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, null); + logger.error("Unexpected error: {}", message); + } + return error; + } + + private ProcessError toProcessErrorRemote(SocketTimeoutException timeoutException, String process) + { + ProcessError error; + String message = timeoutException.getMessage().toLowerCase(Locale.ROOT); + if (message.contains("connect")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + else if (message.contains("read")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_TIMEOUT_READ, + ConstantsPing.POTENTIAL_FIX_URL_READ_TIMEOUT); + } + else + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_UNKNOWN, null); + logger.error("Unexpected error: {}", message); + } + return error; + } + + private ProcessError toProcessErrorLocalHttpHostConnect(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_HTTP_HOST_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_REFUSED); + } + + private ProcessError toProcessErrorRemoteHttpHostConnect(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_POST_HTTP_HOST_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_REFUSED); + } + + private ProcessError toProcessErrorLocalConnectTimeout(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + + private ProcessError toProcessErrorRemoteConnectTimeout(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_POST_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + + private long getDownloadResourceSize(Variables variables) + { + return variables.getLong(ExecutionVariables.downloadResourceSizeBytes.name()); + } + + private void setDownloadResourceSizeBytes(Variables variables, long resourceSizeBytes) + { + variables.setLong(ExecutionVariables.downloadResourceSizeBytes.name(), resourceSizeBytes); + } + + private IdType storeBinary(RandomByteInputStream downloadResourceContent) + { + return api.getFhirWebserviceClientProvider().getLocalWebserviceClient().withMinimalReturn().createBinary( + downloadResourceContent, ConstantsPing.DOWNLOAD_RESOURCE_MIME_TYPE, + api.getOrganizationProvider().getLocalOrganization().get().getIdElement().getValue()); + } + + public void setProcess(Expression process) + { + this.process = process; + } +} diff --git a/src/main/java/dev/dsf/bpe/service/LogNoResponse.java b/src/main/java/dev/dsf/bpe/service/LogNoResponse.java deleted file mode 100644 index b8ec0cc3..00000000 --- a/src/main/java/dev/dsf/bpe/service/LogNoResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -package dev.dsf.bpe.service; - -import org.camunda.bpm.engine.delegate.BpmnError; -import org.camunda.bpm.engine.delegate.DelegateExecution; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import dev.dsf.bpe.ConstantsPing; -import dev.dsf.bpe.v1.ProcessPluginApi; -import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; -import dev.dsf.bpe.v1.variables.Target; -import dev.dsf.bpe.v1.variables.Variables; - -public class LogNoResponse extends AbstractServiceDelegate -{ - private static final Logger logger = LoggerFactory.getLogger(LogNoResponse.class); - - public LogNoResponse(ProcessPluginApi api) - { - super(api); - } - - @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception - { - Target target = variables.getTarget(); - - logger.warn("PONG from organization {} (endpoint {}) missing", target.getOrganizationIdentifierValue(), - target.getEndpointIdentifierValue()); - - variables.setString("statusCode_" + target.getCorrelationKey(), - ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_PONG_MISSING); - } -} diff --git a/src/main/java/dev/dsf/bpe/service/LogPong.java b/src/main/java/dev/dsf/bpe/service/LogPong.java deleted file mode 100644 index 72fa6168..00000000 --- a/src/main/java/dev/dsf/bpe/service/LogPong.java +++ /dev/null @@ -1,36 +0,0 @@ -package dev.dsf.bpe.service; - -import org.camunda.bpm.engine.delegate.BpmnError; -import org.camunda.bpm.engine.delegate.DelegateExecution; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import dev.dsf.bpe.ConstantsPing; -import dev.dsf.bpe.v1.ProcessPluginApi; -import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; -import dev.dsf.bpe.v1.variables.Target; -import dev.dsf.bpe.v1.variables.Variables; - -public class LogPong extends AbstractServiceDelegate -{ - private static final Logger logger = LoggerFactory.getLogger(LogPong.class); - - public LogPong(ProcessPluginApi api) - { - super(api); - - } - - @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception - { - Target target = variables.getTarget(); - - logger.info("PONG from {} (endpoint: {})", target.getOrganizationIdentifierValue(), - target.getEndpointIdentifierValue()); - - execution.removeVariable("statusCode"); - variables.setString("statusCode_" + target.getCorrelationKey(), - ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_PONG_RECEIVED); - } -} diff --git a/src/main/java/dev/dsf/bpe/service/LogSendError.java b/src/main/java/dev/dsf/bpe/service/LogSendError.java deleted file mode 100644 index ed06bfcc..00000000 --- a/src/main/java/dev/dsf/bpe/service/LogSendError.java +++ /dev/null @@ -1,35 +0,0 @@ -package dev.dsf.bpe.service; - -import org.camunda.bpm.engine.delegate.BpmnError; -import org.camunda.bpm.engine.delegate.DelegateExecution; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import dev.dsf.bpe.v1.ProcessPluginApi; -import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; -import dev.dsf.bpe.v1.variables.Target; -import dev.dsf.bpe.v1.variables.Variables; - -public class LogSendError extends AbstractServiceDelegate -{ - private static final Logger logger = LoggerFactory.getLogger(LogSendError.class); - - public LogSendError(ProcessPluginApi api) - { - super(api); - } - - @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception - { - Target target = variables.getTarget(); - String statusCode = (String) execution.getVariableLocal("statusCode"); - String errorMessage = (String) execution.getVariableLocal("errorMessage"); - - logger.warn("Unable to send PING to {} (endpoint: {}): {}", target.getOrganizationIdentifierValue(), - target.getEndpointIdentifierValue(), errorMessage); - - variables.setString("statusCode_" + target.getCorrelationKey(), statusCode); - variables.setString("errorMessage_" + target.getCorrelationKey(), errorMessage); - } -} diff --git a/src/main/java/dev/dsf/bpe/service/RandomByteInputStream.java b/src/main/java/dev/dsf/bpe/service/RandomByteInputStream.java new file mode 100644 index 00000000..c651df36 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/RandomByteInputStream.java @@ -0,0 +1,150 @@ +package dev.dsf.bpe.service; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Random; + +public class RandomByteInputStream extends InputStream +{ + private final long numBytes; + private long numBytesRead = 0; + private final Random random; + private boolean closed = false; + + public RandomByteInputStream(long numBytes) + { + this.numBytes = numBytes; + this.random = new Random(); + } + + @Override + public synchronized int read() + { + if (!closed && numBytesRead < numBytes) + { + int next = random.nextInt(255); + numBytesRead++; + return next; + } + else + { + this.close(); + return -1; + } + } + + @Override + public int read(byte[] b) throws IOException + { + return read(b, 0, b.length); + } + + @Override + public synchronized int read(byte[] b, int off, int len) + { + if (len == 0) + return 0; + if (closed || numBytesRead >= numBytes) + { + this.close(); + return -1; + } + int localNumBytesRead = 0; + for (int i = 0; i < Math.min(len, b.length - off); i++) + { + if (!closed && numBytesRead < numBytes) + { + byte next = (byte) random.nextInt(255); + b[off + localNumBytesRead] = next; + localNumBytesRead++; + numBytesRead++; + } + else + { + this.close(); + break; + } + } + if (localNumBytesRead == numBytes) + this.close(); + return localNumBytesRead; + } + + @Override + public synchronized byte[] readAllBytes() + { + if (numBytes <= Integer.MAX_VALUE) + { + byte[] b = new byte[(int) numBytes]; + read(b, 0, (int) (numBytes - numBytesRead)); + return b; + } + else + { + throw new UnsupportedOperationException( + "JVM does not support array lengths longer than Integer.MAX_VALUE values"); + } + } + + @Override + public int readNBytes(byte[] b, int off, int len) + { + return read(b, off, len); + } + + @Override + public synchronized long transferTo(OutputStream out) throws IOException + { + return super.transferTo(out); + } + + @Override + public synchronized long skip(long n) + { + if (n <= 0) + return 0; + long skippableBytes = numBytes - numBytesRead; + if (skippableBytes < n) + { + numBytesRead += skippableBytes; + return skippableBytes; + } + else + { + numBytesRead += n; + return n; + } + } + + @Override + public synchronized int available() throws IOException + { + if (closed || numBytesRead >= numBytes) + throw new IOException("Stream is closed"); + return (int) (numBytes - numBytesRead); + } + + @Override + public void mark(int readAheadLimit) + { + throw new UnsupportedOperationException("RandomByteInputStream does not support mark/reset"); + } + + @Override + public synchronized void reset() + { + throw new UnsupportedOperationException("RandomByteInputStream does not support mark/reset"); + } + + @Override + public void close() + { + this.closed = true; + } + + public boolean isClosed() + { + return closed; + } +} diff --git a/src/main/java/dev/dsf/bpe/service/SaveResults.java b/src/main/java/dev/dsf/bpe/service/SaveResults.java deleted file mode 100644 index 1edc5575..00000000 --- a/src/main/java/dev/dsf/bpe/service/SaveResults.java +++ /dev/null @@ -1,77 +0,0 @@ -package dev.dsf.bpe.service; - -import java.util.Comparator; -import java.util.Objects; - -import org.camunda.bpm.engine.delegate.BpmnError; -import org.camunda.bpm.engine.delegate.DelegateExecution; -import org.hl7.fhir.r4.model.Task; -import org.springframework.beans.factory.InitializingBean; - -import dev.dsf.bpe.ConstantsPing; -import dev.dsf.bpe.mail.ErrorMailService; -import dev.dsf.bpe.util.PingStatusGenerator; -import dev.dsf.bpe.v1.ProcessPluginApi; -import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; -import dev.dsf.bpe.v1.variables.Target; -import dev.dsf.bpe.v1.variables.Targets; -import dev.dsf.bpe.v1.variables.Variables; - -public class SaveResults extends AbstractServiceDelegate implements InitializingBean -{ - private final PingStatusGenerator statusGenerator; - private final ErrorMailService errorMailService; - - public SaveResults(ProcessPluginApi api, PingStatusGenerator statusGenerator, ErrorMailService errorMailService) - { - super(api); - - this.statusGenerator = statusGenerator; - this.errorMailService = errorMailService; - } - - @Override - public void afterPropertiesSet() throws Exception - { - super.afterPropertiesSet(); - - Objects.requireNonNull(statusGenerator, "statusGenerator"); - Objects.requireNonNull(errorMailService, "errorMailService"); - } - - @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception - { - Task task = variables.getStartTask(); - Targets targets = variables.getTargets(); - - targets.getEntries().stream().sorted(Comparator.comparing(Target::getEndpointIdentifierValue)).forEach(target -> - { - String correlationKey = target.getCorrelationKey(); - - String statusCode = variables.getString("statusCode_" + correlationKey); - if (ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_REACHABLE.equals(statusCode) - || ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_ALLOWED.equals(statusCode)) - { - String errorMessage = variables.getString("errorMessage_" + correlationKey); - task.addOutput(statusGenerator.createPingStatusOutput(target, statusCode, errorMessage)); - - if (ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_REACHABLE.equals(statusCode)) - errorMailService.endpointNotReachableForPing(task.getIdElement(), target, errorMessage); - else if (ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_ALLOWED.equals(statusCode)) - errorMailService.endpointReachablePingForbidden(task.getIdElement(), target, errorMessage); - } - else - { - task.addOutput(statusGenerator.createPingStatusOutput(target, statusCode)); - - if (ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_PONG_MISSING.equals(statusCode)) - errorMailService.pongMessageNotReceived(task.getIdElement(), target); - } - }); - - // TODO only send one combined status mail - - variables.updateTask(task); - } -} diff --git a/src/main/java/dev/dsf/bpe/service/SetDownloadResourceSize.java b/src/main/java/dev/dsf/bpe/service/SetDownloadResourceSize.java new file mode 100644 index 00000000..892507a6 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/SetDownloadResourceSize.java @@ -0,0 +1,50 @@ +package dev.dsf.bpe.service; + +import java.util.Optional; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.DecimalType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class SetDownloadResourceSize extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(SetDownloadResourceSize.class); + private final long maxDownloadResourceSizeBytes; + + public SetDownloadResourceSize(ProcessPluginApi api, long maxDownloadResourceSizeBytes) + { + super(api); + this.maxDownloadResourceSizeBytes = Math.min(maxDownloadResourceSizeBytes, + ConstantsPing.DOWNLOAD_RESOURCE_SIZE_BYTES_DEFAULT); + } + + @Override + protected void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + logger.debug("Setting download resource size..."); + + long downloadResourceSizeBytes = getDownloadResourceSizeBytes(variables); + variables.setLong(ExecutionVariables.downloadResourceSizeBytes.name(), downloadResourceSizeBytes); + + logger.debug("Set download resource size to " + downloadResourceSizeBytes); + } + + private long getDownloadResourceSizeBytes(Variables variables) + { + Optional downloadResourceSizeType = api.getTaskHelper().getFirstInputParameterValue( + variables.getStartTask(), CodeSystem.DsfPing.URL, + CodeSystem.DsfPing.Code.DOWNLOAD_RESOURCE_SIZE_BYTES.getValue(), DecimalType.class); + + return downloadResourceSizeType.map(decimalType -> decimalType.getValue().longValue()) + .orElse(maxDownloadResourceSizeBytes); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/SetTargetAndConfigureTimer.java b/src/main/java/dev/dsf/bpe/service/autostart/SetTargetAndConfigureTimer.java similarity index 74% rename from src/main/java/dev/dsf/bpe/service/SetTargetAndConfigureTimer.java rename to src/main/java/dev/dsf/bpe/service/autostart/SetTargetAndConfigureTimer.java index 4df0f1a4..0effa8df 100644 --- a/src/main/java/dev/dsf/bpe/service/SetTargetAndConfigureTimer.java +++ b/src/main/java/dev/dsf/bpe/service/autostart/SetTargetAndConfigureTimer.java @@ -1,11 +1,13 @@ -package dev.dsf.bpe.service; +package dev.dsf.bpe.service.autostart; import org.camunda.bpm.engine.delegate.BpmnError; import org.camunda.bpm.engine.delegate.DelegateExecution; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import dev.dsf.bpe.CodeSystem; import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; import dev.dsf.bpe.v1.ProcessPluginApi; import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; import dev.dsf.bpe.v1.variables.Variables; @@ -20,13 +22,12 @@ public SetTargetAndConfigureTimer(ProcessPluginApi api) } @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError { String timerInterval = getTimerInterval(variables); - logger.debug("Setting variable '{}' to {}", ConstantsPing.BPMN_EXECUTION_VARIABLE_TIMER_INTERVAL, - timerInterval); + logger.debug("Setting variable '{}' to {}", ExecutionVariables.timerInterval.name(), timerInterval); - variables.setString(ConstantsPing.BPMN_EXECUTION_VARIABLE_TIMER_INTERVAL, timerInterval); + variables.setString(ExecutionVariables.timerInterval.name(), timerInterval); variables.setTarget( variables.createTarget(api.getOrganizationProvider().getLocalOrganizationIdentifierValue().get(), api.getEndpointProvider().getLocalEndpointIdentifierValue().get(), @@ -36,8 +37,8 @@ protected void doExecute(DelegateExecution execution, Variables variables) throw private String getTimerInterval(Variables variables) { return api.getTaskHelper() - .getFirstInputParameterStringValue(variables.getStartTask(), ConstantsPing.CODESYSTEM_DSF_PING, - ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TIMER_INTERVAL) + .getFirstInputParameterStringValue(variables.getStartTask(), CodeSystem.DsfPing.URL, + CodeSystem.DsfPing.Code.TIMER_INTERVAL.getValue()) .orElse(ConstantsPing.TIMER_INTERVAL_DEFAULT_VALUE); } } diff --git a/src/main/java/dev/dsf/bpe/service/ping/CheckPingTaskStatus.java b/src/main/java/dev/dsf/bpe/service/ping/CheckPingTaskStatus.java new file mode 100644 index 00000000..5504ed02 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/ping/CheckPingTaskStatus.java @@ -0,0 +1,101 @@ +package dev.dsf.bpe.service.ping; + +import java.util.Objects; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; +import dev.dsf.bpe.variables.codesystem.dsfpingstatus.CodeValueImpl; +import dev.dsf.fhir.client.FhirWebserviceClient; +import jakarta.ws.rs.WebApplicationException; + +public class CheckPingTaskStatus extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(CheckPingTaskStatus.class); + + public CheckPingTaskStatus(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + logger.debug("Checking status of ping task..."); + + Target target = variables.getTarget(); + String correlationKey = target.getCorrelationKey(); + + String taskId = (String) delegateExecution.getVariableLocal(ExecutionVariables.pingTaskId.name()); + + Objects.requireNonNull(taskId); + FhirWebserviceClient fhirWebserviceClient = api.getFhirWebserviceClientProvider() + .getWebserviceClient(target.getEndpointUrl()); + try + { + Task pingTask = fhirWebserviceClient.withRetry(3, 1000).read(Task.class, taskId); + ProcessError error = switch (pingTask.getStatus()) + { + case COMPLETED -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_STATUS_COMPLETED, null); + case FAILED -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_STATUS_FAILED, null); + case INPROGRESS -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_STATUS_IN_PROGRESS, null); + case REQUESTED -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_STATUS_REQUESTED, null); + default -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_STATUS_UNEXPECTED, null); + }; + ErrorListUtils.add(error, delegateExecution, correlationKey); + } + catch (WebApplicationException e) + { + ProcessError error = getProcessError(e); + ErrorListUtils.add(error, delegateExecution, correlationKey); + } + finally + { + variables.setVariable(ExecutionVariables.statusCode.correlatedValue(correlationKey), + new CodeValueImpl(CodeSystem.DsfPingStatus.Code.PONG_MISSING)); + } + + logger.debug("Saved '{}' to process execution for correlation key '{}'", + CodeSystem.DsfPing.Code.ERROR.getValue(), correlationKey); + } + + private static ProcessError getProcessError(WebApplicationException e) + { + int statusCode = e.getResponse().getStatus(); + return switch (statusCode) + { + case 401 -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + case 403 -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + case 500 -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + case 502 -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + default -> new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RESPONSE_MESSAGE_TIMEOUT_HTTP_UNEXPECTED, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + }; + } +} diff --git a/src/main/java/dev/dsf/bpe/service/ping/DownloadResourceAndMeasureSpeedInSubProcess.java b/src/main/java/dev/dsf/bpe/service/ping/DownloadResourceAndMeasureSpeedInSubProcess.java new file mode 100644 index 00000000..f1c00f1b --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/ping/DownloadResourceAndMeasureSpeedInSubProcess.java @@ -0,0 +1,55 @@ +package dev.dsf.bpe.service.ping; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.util.BinaryResourceDownloader; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; +import dev.dsf.bpe.variables.duration.DurationValueImpl; + +public class DownloadResourceAndMeasureSpeedInSubProcess extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(DownloadResourceAndMeasureSpeedInSubProcess.class); + + public DownloadResourceAndMeasureSpeedInSubProcess(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + logger.debug("Starting resource download to measure speed..."); + + Task task = variables.getLatestTask(); + Target target = variables.getTarget(); + String correlationKey = target.getCorrelationKey(); + + + BinaryResourceDownloader.DownloadResult downloadResult = new BinaryResourceDownloader( + ConstantsPing.PROCESS_NAME_PING).download(variables, api, task); + + if (downloadResult.getErrorTuple() == null) + { + variables.setLong(ExecutionVariables.downloadedBytes.correlatedValue(correlationKey), + downloadResult.getDownloadedBytes()); + variables.setVariable(ExecutionVariables.downloadedDuration.correlatedValue(correlationKey), + new DurationValueImpl(downloadResult.getDownloadedDuration())); + } + else + { + delegateExecution.setVariableLocal(ExecutionVariables.resourceDownloadError.name(), + downloadResult.getErrorTuple().errorLocal()); + } + + logger.debug("Completed resource download and measured speed."); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/ping/LogAndSaveError.java b/src/main/java/dev/dsf/bpe/service/ping/LogAndSaveError.java new file mode 100644 index 00000000..2db502fc --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/ping/LogAndSaveError.java @@ -0,0 +1,38 @@ +package dev.dsf.bpe.service.ping; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; + +public class LogAndSaveError extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(LogAndSaveError.class); + + public LogAndSaveError(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + Target target = variables.getTarget(); + + ProcessError error = (ProcessError) delegateExecution + .getVariableLocal(ExecutionVariables.resourceDownloadError.name()); + + ErrorListUtils.add(error, delegateExecution, target.getCorrelationKey()); + + logger.info("Error while trying to download resource from {}: {}", target.getEndpointUrl(), + error.concept().getDisplay()); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/ping/LogAndSaveSendError.java b/src/main/java/dev/dsf/bpe/service/ping/LogAndSaveSendError.java new file mode 100644 index 00000000..753c1d1c --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/ping/LogAndSaveSendError.java @@ -0,0 +1,41 @@ +package dev.dsf.bpe.service.ping; + +import java.util.Objects; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; +import dev.dsf.bpe.variables.codesystem.dsfpingstatus.CodeValueImpl; + +public class LogAndSaveSendError extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(LogAndSaveSendError.class); + + public LogAndSaveSendError(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError + { + String correlationKey = variables.getTarget().getCorrelationKey(); + ProcessError error = (ProcessError) execution.getVariableLocal(ExecutionVariables.error.name()); + CodeSystem.DsfPingStatus.Code status = (CodeSystem.DsfPingStatus.Code) execution + .getVariableLocal(ExecutionVariables.statusCode.name()); + Objects.requireNonNull(status, "status"); + + ErrorListUtils.add(error, execution, correlationKey); + variables.setVariable(ExecutionVariables.statusCode.correlatedValue(correlationKey), new CodeValueImpl(status)); + logger.debug("Saved error when trying to send ping message. Error message: {}", error.concept().getDisplay()); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/ping/LogAndSaveUploadErrorPing.java b/src/main/java/dev/dsf/bpe/service/ping/LogAndSaveUploadErrorPing.java new file mode 100644 index 00000000..be2b2d4c --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/ping/LogAndSaveUploadErrorPing.java @@ -0,0 +1,33 @@ +package dev.dsf.bpe.service.ping; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class LogAndSaveUploadErrorPing extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(LogAndSaveUploadErrorPing.class); + + public LogAndSaveUploadErrorPing(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError + { + ProcessError error = (ProcessError) variables.getVariable(ExecutionVariables.resourceUploadError.name()); + + ErrorListUtils.add(error, execution); + + logger.info("Error while storing binary resource for download: {}", error.concept().getDisplay()); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/ping/SavePong.java b/src/main/java/dev/dsf/bpe/service/ping/SavePong.java new file mode 100644 index 00000000..9e940c75 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/ping/SavePong.java @@ -0,0 +1,81 @@ +package dev.dsf.bpe.service.ping; + +import java.time.Duration; +import java.util.List; +import java.util.Optional; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.ProcessErrors; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; +import dev.dsf.bpe.variables.codesystem.dsfpingstatus.CodeValueImpl; +import dev.dsf.bpe.variables.duration.DurationValueImpl; + +public class SavePong extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(SavePong.class); + + public SavePong(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + Target target = variables.getTarget(); + logger.debug("Pong received from {}. Saving pong information...", target.getEndpointUrl()); + String correlationKey = target.getCorrelationKey(); + delegateExecution.removeVariable("statusCode"); + + Task pong = variables.getLatestTask(); + + Optional optDownloadedDuration = api.getTaskHelper() + .getFirstInputParameterValue(pong, CodeSystem.DsfPing.URL, + CodeSystem.DsfPing.Code.DOWNLOADED_DURATION_MILLIS.getValue(), + org.hl7.fhir.r4.model.Duration.class); + optDownloadedDuration.ifPresent( + duration -> variables.setVariable(ExecutionVariables.uploadedDuration.correlatedValue(correlationKey), + new DurationValueImpl(Duration.ofMillis(duration.getValue().longValue())))); + + Optional optDownloadedBytes = api.getTaskHelper().getFirstInputParameterValue(pong, + CodeSystem.DsfPing.URL, CodeSystem.DsfPing.Code.DOWNLOADED_BYTES.getValue(), DecimalType.class); + optDownloadedBytes.ifPresent(decimalType -> variables.setLong( + ExecutionVariables.uploadedBytes.correlatedValue(correlationKey), decimalType.getValue().longValue())); + + + ProcessErrors errorList = new ProcessErrors(parseInputs(pong)); + + ErrorListUtils.addAll(errorList, delegateExecution, correlationKey); + variables.setVariable(ExecutionVariables.statusCode.correlatedValue(correlationKey), + new CodeValueImpl(CodeSystem.DsfPingStatus.Code.PONG_RECEIVED)); + + logger.debug("Saved pong information."); + } + + private List parseInputs(Task task) + { + List inputs = task.getInput().stream().filter( + input -> CodeSystem.DsfPing.Code.ERROR.getValue().equals(input.getType().getCodingFirstRep().getCode())) + .toList(); + + return inputs.stream() + .map(input -> ProcessError.toError( + input.getExtensionByUrl(ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_ERROR), + ConstantsPing.PROCESS_NAME_PONG)) + .toList(); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/SelectPingTargets.java b/src/main/java/dev/dsf/bpe/service/ping/SelectPingTargets.java similarity index 93% rename from src/main/java/dev/dsf/bpe/service/SelectPingTargets.java rename to src/main/java/dev/dsf/bpe/service/ping/SelectPingTargets.java index ff52d009..e9f16e5f 100644 --- a/src/main/java/dev/dsf/bpe/service/SelectPingTargets.java +++ b/src/main/java/dev/dsf/bpe/service/ping/SelectPingTargets.java @@ -1,4 +1,4 @@ -package dev.dsf.bpe.service; +package dev.dsf.bpe.service.ping; import java.util.Collections; import java.util.HashMap; @@ -28,7 +28,7 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; -import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.CodeSystem; import dev.dsf.bpe.v1.ProcessPluginApi; import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; import dev.dsf.bpe.v1.constants.NamingSystems.EndpointIdentifier; @@ -39,7 +39,6 @@ public class SelectPingTargets extends AbstractServiceDelegate implements InitializingBean { private static final Logger logger = LoggerFactory.getLogger(SelectPingTargets.class); - private static final Pattern endpointResouceTypes = Pattern.compile( "Endpoint|HealthcareService|ImagingStudy|InsurancePlan|Location|Organization|OrganizationAffiliation|PractitionerRole"); @@ -49,10 +48,12 @@ public SelectPingTargets(ProcessPluginApi api) } @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError { - Stream targetEndpoints = getTargetEndpointsSearchParameter(variables).map(this::searchForEndpoints) - .orElse(allEndpoints()).filter(isLocalEndpoint().negate()); + Task startTask = variables.getStartTask(); + Stream targetEndpoints = getTargetEndpointsSearchParameter(variables) + .map(uriComponents -> searchForEndpoints(uriComponents)).orElse(allEndpoints()) + .filter(isLocalEndpoint().negate()); List remoteOrganizations = api.getOrganizationProvider().getRemoteOrganizations(); Map organizationIdentifierByOrganizationId = remoteOrganizations.stream().collect( @@ -78,8 +79,8 @@ private Optional getTargetEndpointsSearchParameter(Variables vari { Task mainTask = variables.getStartTask(); return api.getTaskHelper() - .getFirstInputParameterStringValue(mainTask, ConstantsPing.CODESYSTEM_DSF_PING, - ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TARGET_ENDPOINTS) + .getFirstInputParameterStringValue(mainTask, CodeSystem.DsfPing.URL, + CodeSystem.DsfPing.Code.TARGET_ENDPOINTS.getValue()) .map(requestUrl -> UriComponentsBuilder.fromUriString(requestUrl).build()); } @@ -97,7 +98,7 @@ private Stream searchForEndpoints(UriComponents searchParameters, int if (resourceType.isEmpty()) return Stream.empty(); - Map> queryParameters = new HashMap>(); + Map> queryParameters = new HashMap<>(); queryParameters.putAll(searchParameters.getQueryParams()); queryParameters.put("_page", Collections.singletonList(String.valueOf(page))); diff --git a/src/main/java/dev/dsf/bpe/service/ping/SetPongTimeoutDuration.java b/src/main/java/dev/dsf/bpe/service/ping/SetPongTimeoutDuration.java new file mode 100644 index 00000000..7faa80fb --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/ping/SetPongTimeoutDuration.java @@ -0,0 +1,65 @@ +package dev.dsf.bpe.service.ping; + +import java.time.Duration; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class SetPongTimeoutDuration extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(SetPongTimeoutDuration.class); + + public SetPongTimeoutDuration(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws Exception + { + logger.debug("Setting pong timer duration..."); + Task startTask = variables.getStartTask(); + Optional optPongTimeoutDuration = api.getTaskHelper().getFirstInputParameterStringValue(startTask, + CodeSystem.DsfPing.URL, CodeSystem.DsfPing.Code.PONG_TIMEOUT_DURATION_ISO_8601.getValue()); + if (optPongTimeoutDuration.isPresent()) + { + String pongTimeoutDuration = optPongTimeoutDuration.get(); + if (isDurationValid(pongTimeoutDuration)) + { + variables.setString(ExecutionVariables.pongTimerDuration.name(), pongTimeoutDuration); + logger.debug("Pong timer duration set to {}", pongTimeoutDuration); + } + else + { + throw new RuntimeException("Pong timeout duration is invalid"); + } + } + else + { + logger.debug("No pong timeout duration specified"); + } + } + + private boolean isDurationValid(String duration) + { + try + { + Duration.parse(duration); + return true; + } + catch (DateTimeParseException e) + { + return false; + } + } +} diff --git a/src/main/java/dev/dsf/bpe/service/ping/StoreResults.java b/src/main/java/dev/dsf/bpe/service/ping/StoreResults.java new file mode 100644 index 00000000..1fd5c75c --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/ping/StoreResults.java @@ -0,0 +1,139 @@ +package dev.dsf.bpe.service.ping; + +import java.math.BigDecimal; +import java.time.Duration; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.InitializingBean; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.ProcessErrors; +import dev.dsf.bpe.mail.AggregateErrorMailService; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.util.task.output.generator.PingStatusGenerator; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Targets; +import dev.dsf.bpe.v1.variables.Variables; + +public class StoreResults extends AbstractServiceDelegate implements InitializingBean +{ + private static final Logger logger = LoggerFactory.getLogger(StoreResults.class); + private final AggregateErrorMailService errorMailService; + private CodeSystem.DsfPingUnits.Code networkSpeedUnit; + + public StoreResults(ProcessPluginApi api, AggregateErrorMailService errorMailService, + CodeSystem.DsfPingUnits.Code networkSpeedUnit) + { + super(api); + this.networkSpeedUnit = networkSpeedUnit; + this.errorMailService = errorMailService; + } + + @Override + public void afterPropertiesSet() throws Exception + { + super.afterPropertiesSet(); + + Objects.requireNonNull(errorMailService, "errorMailService"); + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError + { + logger.debug("Storing results for process started with Task {}", + variables.getStartTask().getIdElement().getValue()); + Task task = variables.getStartTask(); + Targets targets = variables.getTargets(); + Map> errorsPerTarget = new HashMap<>(); + + ProcessError.toTaskOutput(ErrorListUtils.getErrorList(execution).getEntries()).forEach(task::addOutput); + + targets.getEntries().stream().sorted(Comparator.comparing(Target::getEndpointIdentifierValue)).forEach(target -> + { + String correlationKey = target.getCorrelationKey(); + + ProcessErrors errors = ErrorListUtils.getErrorList(execution, correlationKey); + CodeSystem.DsfPingStatus.Code statusCode = (CodeSystem.DsfPingStatus.Code) variables + .getVariable(ExecutionVariables.statusCode.correlatedValue(correlationKey)); + long downloadResourceSizeBytes = variables.getLong(ExecutionVariables.downloadResourceSizeBytes.name()); + if (downloadResourceSizeBytes >= 0) // if fat-ping + { + Long downloadedBytes = variables + .getLong(ExecutionVariables.downloadedBytes.correlatedValue(correlationKey)); + Duration downloadedDuration = (Duration) variables + .getVariable(ExecutionVariables.downloadedDuration.correlatedValue(correlationKey)); + + Optional downloadSpeedAndUnit = calculateNetworkSpeed( + downloadedBytes, downloadedDuration); + BigDecimal downloadSpeed = downloadSpeedAndUnit.map(CodeSystem.DsfPingUnits.Code.SpeedAndUnit::speed) + .orElse(null); + CodeSystem.DsfPingUnits.Code downloadSpeedUnit = downloadSpeedAndUnit + .map(CodeSystem.DsfPingUnits.Code.SpeedAndUnit::unit).orElse(null); + + Long uploadedBytes = variables + .getLong(ExecutionVariables.uploadedBytes.correlatedValue(correlationKey)); + Duration uploadedDurationMillis = (Duration) variables + .getVariable(ExecutionVariables.uploadedDuration.correlatedValue(correlationKey)); + + Optional uploadSpeedAndUnit = calculateNetworkSpeed( + uploadedBytes, uploadedDurationMillis); + BigDecimal uploadSpeed = uploadSpeedAndUnit.map(CodeSystem.DsfPingUnits.Code.SpeedAndUnit::speed) + .orElse(null); + CodeSystem.DsfPingUnits.Code uploadSpeedUnit = uploadSpeedAndUnit + .map(CodeSystem.DsfPingUnits.Code.SpeedAndUnit::unit).orElse(null); + + + task.addOutput(PingStatusGenerator.createPingStatusOutput(target, statusCode, errors.getEntries(), + downloadSpeed, downloadSpeedUnit, uploadSpeed, uploadSpeedUnit)); + } + else // if slim-ping + { + task.addOutput(PingStatusGenerator.createPingStatusOutput(target, statusCode, errors.getEntries())); + } + errorsPerTarget.put(target, errors.getEntries()); + }); + + variables.updateTask(task); + + errorMailService.send(task.getIdElement(), errorsPerTarget); + + logger.debug("Successfully stored results for task {}", variables.getStartTask().getIdElement().getValue()); + } + + private Optional calculateNetworkSpeed(Long transferredBytes, + Duration transferDuration) + { + if (transferredBytes != null && transferDuration != null) + { + if (Objects.isNull(networkSpeedUnit)) + { + return Optional.of( + CodeSystem.DsfPingUnits.Code.calculateSpeedWithFittingUnit(transferredBytes, transferDuration)); + } + else + { + return Optional.of(new CodeSystem.DsfPingUnits.Code.SpeedAndUnit( + networkSpeedUnit.calculateSpeed(transferredBytes, transferDuration), networkSpeedUnit)); + } + } + else + { + return Optional.empty(); + } + } +} diff --git a/src/main/java/dev/dsf/bpe/service/pong/DownloadResourceAndMeasureSpeed.java b/src/main/java/dev/dsf/bpe/service/pong/DownloadResourceAndMeasureSpeed.java new file mode 100644 index 00000000..bf7b04ff --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/pong/DownloadResourceAndMeasureSpeed.java @@ -0,0 +1,51 @@ +package dev.dsf.bpe.service.pong; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.util.BinaryResourceDownloader; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; +import dev.dsf.bpe.variables.duration.DurationValueImpl; + +public class DownloadResourceAndMeasureSpeed extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(DownloadResourceAndMeasureSpeed.class); + + public DownloadResourceAndMeasureSpeed(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + logger.debug("Starting resource download to measure speed..."); + + Task task = variables.getStartTask(); + + BinaryResourceDownloader.DownloadResult downloadResult = new BinaryResourceDownloader( + ConstantsPing.PROCESS_NAME_PONG).download(variables, api, task); + + if (downloadResult.getErrorTuple() == null) + { + variables.setLong(ExecutionVariables.downloadedBytes.name(), downloadResult.getDownloadedBytes()); + variables.setVariable(ExecutionVariables.downloadedDuration.name(), + new DurationValueImpl(downloadResult.getDownloadedDuration())); + } + else + { + delegateExecution.setVariable(ExecutionVariables.resourceDownloadError.name(), + downloadResult.getErrorTuple().errorLocal()); + delegateExecution.setVariable(ExecutionVariables.resourceDownloadErrorRemote.name(), + downloadResult.getErrorTuple().errorRemote()); + } + logger.debug("Completed resource download and measured speed."); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/pong/EstimateCleanupTimerDuration.java b/src/main/java/dev/dsf/bpe/service/pong/EstimateCleanupTimerDuration.java new file mode 100644 index 00000000..1f5cb891 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/pong/EstimateCleanupTimerDuration.java @@ -0,0 +1,42 @@ +package dev.dsf.bpe.service.pong; + +import java.time.Duration; +import java.util.Optional; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class EstimateCleanupTimerDuration extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(EstimateCleanupTimerDuration.class); + + public EstimateCleanupTimerDuration(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + logger.debug("Estimating cleanup timer duration..."); + final long minTimerDurationMillis = 20000; + Duration downloadedDuration = Optional + .ofNullable((Duration) variables.getVariable(ExecutionVariables.downloadedDuration.name())) + .orElse(Duration.ZERO); + long timerDurationMillis = downloadedDuration.toMillis() > Long.MAX_VALUE / 10 - minTimerDurationMillis + ? Long.MAX_VALUE + : downloadedDuration.toMillis() * 10 + minTimerDurationMillis; + + String cleanUpTimerDuration = Duration.ofMillis(timerDurationMillis).toString(); + variables.setString("cleanupTimerDuration", cleanUpTimerDuration); + + logger.debug("Estimated cleanup timer duration as {}", cleanUpTimerDuration); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/pong/LogAndSaveAndStoreError.java b/src/main/java/dev/dsf/bpe/service/pong/LogAndSaveAndStoreError.java new file mode 100644 index 00000000..49455dce --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/pong/LogAndSaveAndStoreError.java @@ -0,0 +1,48 @@ +package dev.dsf.bpe.service.pong; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.util.task.output.generator.PingStatusGenerator; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Target; +import dev.dsf.bpe.v1.variables.Variables; + +public class LogAndSaveAndStoreError extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(LogAndSaveAndStoreError.class); + + public LogAndSaveAndStoreError(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + Target target = variables.getTarget(); + Task startTask = variables.getStartTask(); + + ProcessError error = (ProcessError) delegateExecution + .getVariable(ExecutionVariables.resourceDownloadError.name()); + ErrorListUtils.add(error, delegateExecution); + + ProcessError errorRemote = (ProcessError) delegateExecution + .getVariable(ExecutionVariables.resourceDownloadErrorRemote.name()); + ErrorListUtils.addRemote(errorRemote, delegateExecution); + + PingStatusGenerator.updatePongStatusOutput(startTask, + ErrorListUtils.getErrorList(delegateExecution).getEntries()); + variables.updateTask(startTask); + + logger.info("Error while trying to download resource from {}: {}", target.getEndpointUrl(), + error.concept().getDisplay()); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/pong/LogAndSaveUploadErrorPong.java b/src/main/java/dev/dsf/bpe/service/pong/LogAndSaveUploadErrorPong.java new file mode 100644 index 00000000..4a69f96c --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/pong/LogAndSaveUploadErrorPong.java @@ -0,0 +1,36 @@ +package dev.dsf.bpe.service.pong; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class LogAndSaveUploadErrorPong extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(LogAndSaveUploadErrorPong.class); + + public LogAndSaveUploadErrorPong(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError + { + ProcessError error = (ProcessError) variables.getVariable(ExecutionVariables.resourceUploadError.name()); + ErrorListUtils.add(error, execution); + + ProcessError errorRemote = (ProcessError) variables + .getVariable(ExecutionVariables.resourceUploadErrorRemote.name()); + ErrorListUtils.addRemote(errorRemote, execution); + + logger.info("Error while storing binary resource for download: {}", error.concept().getDisplay()); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/LogPing.java b/src/main/java/dev/dsf/bpe/service/pong/LogPing.java similarity index 79% rename from src/main/java/dev/dsf/bpe/service/LogPing.java rename to src/main/java/dev/dsf/bpe/service/pong/LogPing.java index cbfc2867..6e6e21c4 100644 --- a/src/main/java/dev/dsf/bpe/service/LogPing.java +++ b/src/main/java/dev/dsf/bpe/service/pong/LogPing.java @@ -1,4 +1,4 @@ -package dev.dsf.bpe.service; +package dev.dsf.bpe.service.pong; import org.camunda.bpm.engine.delegate.BpmnError; import org.camunda.bpm.engine.delegate.DelegateExecution; @@ -8,7 +8,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.CodeSystem; import dev.dsf.bpe.v1.ProcessPluginApi; import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; import dev.dsf.bpe.v1.variables.Variables; @@ -23,7 +23,7 @@ public LogPing(ProcessPluginApi api) } @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError { Task task = variables.getLatestTask(); @@ -34,8 +34,8 @@ protected void doExecute(DelegateExecution execution, Variables variables) throw private String getEndpointIdentifierValue(Task task) { return api.getTaskHelper() - .getFirstInputParameterValue(task, ConstantsPing.CODESYSTEM_DSF_PING, - ConstantsPing.CODESYSTEM_DSF_PING_VALUE_ENDPOINT_IDENTIFIER, Reference.class) + .getFirstInputParameterValue(task, CodeSystem.DsfPing.URL, + CodeSystem.DsfPing.Code.ENDPOINT_IDENTIFIER.getValue(), Reference.class) .map(Reference::getIdentifier).map(Identifier::getValue).get(); } } diff --git a/src/main/java/dev/dsf/bpe/service/pong/SaveTimeoutError.java b/src/main/java/dev/dsf/bpe/service/pong/SaveTimeoutError.java new file mode 100644 index 00000000..79a772dd --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/pong/SaveTimeoutError.java @@ -0,0 +1,37 @@ +package dev.dsf.bpe.service.pong; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class SaveTimeoutError extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(SaveTimeoutError.class); + + public SaveTimeoutError(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError + { + logger.debug("Storing timeout error..."); + + ProcessError error = new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.CLEANUP_MESSAGE_TIMEOUT, null); + + ErrorListUtils.add(error, execution); + + logger.debug("Stored timeout error: {}", error.concept().getDisplay()); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/SelectPongTarget.java b/src/main/java/dev/dsf/bpe/service/pong/SelectPongTarget.java similarity index 73% rename from src/main/java/dev/dsf/bpe/service/SelectPongTarget.java rename to src/main/java/dev/dsf/bpe/service/pong/SelectPongTarget.java index 7e3f0d3d..71a87488 100644 --- a/src/main/java/dev/dsf/bpe/service/SelectPongTarget.java +++ b/src/main/java/dev/dsf/bpe/service/pong/SelectPongTarget.java @@ -1,15 +1,13 @@ -package dev.dsf.bpe.service; +package dev.dsf.bpe.service.pong; import org.camunda.bpm.engine.delegate.BpmnError; import org.camunda.bpm.engine.delegate.DelegateExecution; -import org.hl7.fhir.r4.model.Identifier; -import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.Task; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; -import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; import dev.dsf.bpe.v1.ProcessPluginApi; import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; import dev.dsf.bpe.v1.constants.CodeSystems.BpmnMessage; @@ -25,14 +23,16 @@ public SelectPongTarget(ProcessPluginApi api) } @Override - protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError, Exception + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError { + logger.debug("Selecting pong targets..."); + Task task = variables.getStartTask(); String correlationKey = api.getTaskHelper() .getFirstInputParameterStringValue(task, BpmnMessage.URL, BpmnMessage.Codes.CORRELATION_KEY).get(); String targetOrganizationIdentifierValue = task.getRequester().getIdentifier().getValue(); - String targetEndpointIdentifierValue = getEndpointIdentifierValue(task); + String targetEndpointIdentifierValue = variables.getString(ExecutionVariables.targetEndpointIdentifier.name()); String targetEndpointAddress = api.getEndpointProvider().getEndpointAddress(targetEndpointIdentifierValue) .orElseThrow(() -> @@ -45,13 +45,6 @@ protected void doExecute(DelegateExecution execution, Variables variables) throw variables.setTarget(variables.createTarget(targetOrganizationIdentifierValue, targetEndpointIdentifierValue, targetEndpointAddress, correlationKey)); - } - - private String getEndpointIdentifierValue(Task task) - { - return api.getTaskHelper() - .getFirstInputParameterValue(task, ConstantsPing.CODESYSTEM_DSF_PING, - ConstantsPing.CODESYSTEM_DSF_PING_VALUE_ENDPOINT_IDENTIFIER, Reference.class) - .map(Reference::getIdentifier).map(Identifier::getValue).get(); + logger.debug("Selected pong targets."); } } diff --git a/src/main/java/dev/dsf/bpe/service/pong/SetEndpointIdentifier.java b/src/main/java/dev/dsf/bpe/service/pong/SetEndpointIdentifier.java new file mode 100644 index 00000000..89b0ef46 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/pong/SetEndpointIdentifier.java @@ -0,0 +1,45 @@ +package dev.dsf.bpe.service.pong; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class SetEndpointIdentifier extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(SetEndpointIdentifier.class); + + public SetEndpointIdentifier(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError + { + logger.debug("Setting endpoint identifier..."); + + Task task = variables.getStartTask(); + String endpointIdentifierValue = getEndpointIdentifierValue(task); + variables.setString(ExecutionVariables.targetEndpointIdentifier.name(), endpointIdentifierValue); + + logger.debug("Set endpoint identifier to " + endpointIdentifierValue); + } + + private String getEndpointIdentifierValue(Task task) + { + return api.getTaskHelper() + .getFirstInputParameterValue(task, CodeSystem.DsfPing.URL, + CodeSystem.DsfPing.Code.ENDPOINT_IDENTIFIER.getValue(), Reference.class) + .map(Reference::getIdentifier).map(Identifier::getValue).get(); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/pong/StoreDownloadSpeed.java b/src/main/java/dev/dsf/bpe/service/pong/StoreDownloadSpeed.java new file mode 100644 index 00000000..95dab46c --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/pong/StoreDownloadSpeed.java @@ -0,0 +1,61 @@ +package dev.dsf.bpe.service.pong; + +import java.math.BigDecimal; +import java.time.Duration; +import java.util.Objects; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.util.task.output.generator.PingStatusGenerator; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class StoreDownloadSpeed extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(StoreDownloadSpeed.class); + private CodeSystem.DsfPingUnits.Code networkSpeedUnit; + + public StoreDownloadSpeed(ProcessPluginApi api, CodeSystem.DsfPingUnits.Code networkSpeedUnit) + { + super(api); + this.networkSpeedUnit = networkSpeedUnit; + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError + { + Task startTask = variables.getStartTask(); + logger.debug("Storing download speed..."); + + long downloadedBytes = variables.getLong(ExecutionVariables.downloadedBytes.name()); + Duration downloadedDuration = (Duration) variables.getVariable(ExecutionVariables.downloadedDuration.name()); + + BigDecimal downloadSpeed = null; + if (downloadedDuration != null) + { + if (Objects.isNull(networkSpeedUnit)) + { + CodeSystem.DsfPingUnits.Code.SpeedAndUnit speedAndUnit = CodeSystem.DsfPingUnits.Code + .calculateSpeedWithFittingUnit(downloadedBytes, downloadedDuration); + downloadSpeed = speedAndUnit.speed(); + networkSpeedUnit = speedAndUnit.unit(); + } + else + { + downloadSpeed = networkSpeedUnit.calculateSpeed(downloadedBytes, downloadedDuration); + } + } + + PingStatusGenerator.updatePongStatusOutputDownloadSpeed(startTask, downloadSpeed, networkSpeedUnit); + + variables.updateTask(startTask); + logger.debug("Stored download speed: " + downloadSpeed + " " + networkSpeedUnit); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/pong/StoreErrors.java b/src/main/java/dev/dsf/bpe/service/pong/StoreErrors.java new file mode 100644 index 00000000..d1e75772 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/pong/StoreErrors.java @@ -0,0 +1,43 @@ +package dev.dsf.bpe.service.pong; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessErrors; +import dev.dsf.bpe.util.ErrorListUtils; +import dev.dsf.bpe.util.task.output.generator.PingStatusGenerator; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class StoreErrors extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(StoreErrors.class); + + public StoreErrors(ProcessPluginApi api) + { + super(api); + } + + @Override + protected void doExecute(DelegateExecution execution, Variables variables) throws BpmnError + { + Task startTask = variables.getStartTask(); + logger.debug("Storing errors..."); + + ProcessErrors errors = ErrorListUtils.getErrorList(execution); + PingStatusGenerator.updatePongStatusOutput(startTask, errors.getEntries()); + + CodeSystem.DsfPingStatus.Code status = (CodeSystem.DsfPingStatus.Code) variables + .getVariable(ExecutionVariables.statusCode.name()); + PingStatusGenerator.updatePongStatusOutput(startTask, status); + + variables.updateTask(startTask); + logger.debug("Stored errors in task: " + startTask.getIdElement().getValue()); + } +} diff --git a/src/main/java/dev/dsf/bpe/service/pong/StoreUploadSpeed.java b/src/main/java/dev/dsf/bpe/service/pong/StoreUploadSpeed.java new file mode 100644 index 00000000..73455be3 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/service/pong/StoreUploadSpeed.java @@ -0,0 +1,80 @@ +package dev.dsf.bpe.service.pong; + +import java.math.BigDecimal; +import java.time.Duration; +import java.util.Objects; +import java.util.Optional; + +import org.camunda.bpm.engine.delegate.BpmnError; +import org.camunda.bpm.engine.delegate.DelegateExecution; +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.PrimitiveType; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.util.task.output.generator.PingStatusGenerator; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.activity.AbstractServiceDelegate; +import dev.dsf.bpe.v1.variables.Variables; + +public class StoreUploadSpeed extends AbstractServiceDelegate +{ + private static final Logger logger = LoggerFactory.getLogger(StoreUploadSpeed.class); + private CodeSystem.DsfPingUnits.Code networkSpeedUnit; + + public StoreUploadSpeed(ProcessPluginApi api, CodeSystem.DsfPingUnits.Code networkSpeedUnit) + { + super(api); + this.networkSpeedUnit = networkSpeedUnit; + } + + @Override + protected void doExecute(DelegateExecution delegateExecution, Variables variables) throws BpmnError + { + Task startTask = variables.getStartTask(); + Task cleanup = variables.getLatestTask(); + logger.debug("Storing upload speed..."); + + Optional uploadedBytesTaskInput = getUploadedBytes(cleanup); + Optional uploadedDurationTaskInput = getUploadedDuration(cleanup); + long uploadedBytes = uploadedBytesTaskInput.map(PrimitiveType::getValue).orElse(BigDecimal.valueOf(0)) + .longValue(); + Duration uploadedDuration = uploadedDurationTaskInput + .map(duration -> Duration.ofMillis(duration.getValue().longValue())).orElse(null); + + BigDecimal uploadSpeed = null; + if (uploadedDuration != null) + { + if (Objects.isNull(networkSpeedUnit)) + { + CodeSystem.DsfPingUnits.Code.SpeedAndUnit speedAndUnit = CodeSystem.DsfPingUnits.Code + .calculateSpeedWithFittingUnit(uploadedBytes, uploadedDuration); + uploadSpeed = speedAndUnit.speed(); + networkSpeedUnit = speedAndUnit.unit(); + } + else + { + uploadSpeed = networkSpeedUnit.calculateSpeed(uploadedBytes, uploadedDuration); + } + } + + PingStatusGenerator.updatePongStatusOutputUploadSpeed(startTask, uploadSpeed, networkSpeedUnit); + + variables.updateTask(startTask); + logger.debug("Stored upload speed: " + uploadSpeed + " " + networkSpeedUnit); + } + + private Optional getUploadedBytes(Task task) + { + return api.getTaskHelper().getFirstInputParameterValue(task, CodeSystem.DsfPing.URL, + CodeSystem.DsfPing.Code.DOWNLOADED_BYTES.getValue(), DecimalType.class); + } + + private Optional getUploadedDuration(Task task) + { + return api.getTaskHelper().getFirstInputParameterValue(task, CodeSystem.DsfPing.URL, + CodeSystem.DsfPing.Code.DOWNLOADED_DURATION_MILLIS.getValue(), org.hl7.fhir.r4.model.Duration.class); + } +} diff --git a/src/main/java/dev/dsf/bpe/spring/config/PingConfig.java b/src/main/java/dev/dsf/bpe/spring/config/PingConfig.java index 85c02919..e80c32a7 100644 --- a/src/main/java/dev/dsf/bpe/spring/config/PingConfig.java +++ b/src/main/java/dev/dsf/bpe/spring/config/PingConfig.java @@ -1,28 +1,52 @@ package dev.dsf.bpe.spring.config; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Scope; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.listener.PingPongProcessPluginDeploymentStateListener; import dev.dsf.bpe.listener.SetCorrelationKeyListener; -import dev.dsf.bpe.mail.ErrorMailService; -import dev.dsf.bpe.message.SendPing; -import dev.dsf.bpe.message.SendPong; +import dev.dsf.bpe.mail.AggregateErrorMailService; +import dev.dsf.bpe.message.CleanupPongMessage; +import dev.dsf.bpe.message.SendPingMessage; +import dev.dsf.bpe.message.SendPongMessage; import dev.dsf.bpe.message.SendStartPing; -import dev.dsf.bpe.service.LogNoResponse; -import dev.dsf.bpe.service.LogPing; -import dev.dsf.bpe.service.LogPong; -import dev.dsf.bpe.service.LogSendError; -import dev.dsf.bpe.service.SaveResults; -import dev.dsf.bpe.service.SelectPingTargets; -import dev.dsf.bpe.service.SelectPongTarget; -import dev.dsf.bpe.service.SetTargetAndConfigureTimer; -import dev.dsf.bpe.util.PingStatusGenerator; +import dev.dsf.bpe.service.Cleanup; +import dev.dsf.bpe.service.GenerateAndStoreResource; +import dev.dsf.bpe.service.SetDownloadResourceSize; +import dev.dsf.bpe.service.autostart.SetTargetAndConfigureTimer; +import dev.dsf.bpe.service.ping.CheckPingTaskStatus; +import dev.dsf.bpe.service.ping.DownloadResourceAndMeasureSpeedInSubProcess; +import dev.dsf.bpe.service.ping.LogAndSaveError; +import dev.dsf.bpe.service.ping.LogAndSaveUploadErrorPing; +import dev.dsf.bpe.service.ping.SavePong; +import dev.dsf.bpe.service.ping.SelectPingTargets; +import dev.dsf.bpe.service.ping.SetPongTimeoutDuration; +import dev.dsf.bpe.service.ping.StoreResults; +import dev.dsf.bpe.service.pong.DownloadResourceAndMeasureSpeed; +import dev.dsf.bpe.service.pong.EstimateCleanupTimerDuration; +import dev.dsf.bpe.service.pong.LogAndSaveAndStoreError; +import dev.dsf.bpe.service.pong.LogAndSaveUploadErrorPong; +import dev.dsf.bpe.service.pong.LogPing; +import dev.dsf.bpe.service.pong.SaveTimeoutError; +import dev.dsf.bpe.service.pong.SelectPongTarget; +import dev.dsf.bpe.service.pong.SetEndpointIdentifier; +import dev.dsf.bpe.service.pong.StoreDownloadSpeed; +import dev.dsf.bpe.service.pong.StoreErrors; +import dev.dsf.bpe.service.pong.StoreUploadSpeed; import dev.dsf.bpe.v1.ProcessPluginApi; import dev.dsf.bpe.v1.documentation.ProcessDocumentation; +import dev.dsf.bpe.variables.duration.DurationValueSerializer; +import dev.dsf.bpe.variables.process_error.ProcessErrorValueSerializer; +import dev.dsf.bpe.variables.process_errors.ProcessErrorsValueSerializer; @Configuration public class PingConfig @@ -30,14 +54,66 @@ public class PingConfig @Autowired private ProcessPluginApi api; - @ProcessDocumentation(description = "To enable a mail being send if the ping process fails, set to 'true'. This requires the SMPT mail service client to be configured in the DSF", processNames = "dsfdev_ping") - @Value("${dev.dsf.dsf.bpe.ping.mail.onPingProcessFailed:false}") + @ProcessDocumentation(description = "To enable a mail being sent if the ping process fails, set to 'true'. This requires the SMPT mail service client to be configured in the DSF", processNames = "dsfdev_ping") + @Value("${dev.dsf.bpe.ping.mail.onPingProcessFailed:false}") private boolean sendPingProcessFailedMail; - @ProcessDocumentation(description = "To enable a mail being send if the pong process fails, set to 'true'. This requires the SMPT mail service client to be configured in the DSF", processNames = "dsfdev_pong") - @Value("${dev.dsf.dsf.bpe.ping.mail.onPongProcessFailed:false}") + @ProcessDocumentation(description = "To enable a mail being sent if the pong process fails, set to 'true'. This requires the SMPT mail service client to be configured in the DSF", processNames = "dsfdev_pong") + @Value("${dev.dsf.bpe.ping.mail.onPongProcessFailed:false}") private boolean sendPongProcessFailedMail; + @ProcessDocumentation(description = "Sets the download limit on resource downloads, essentially limiting the amount of data downloaded from other ping instances. Setting this to a negative value will disable resource downloads, effectively resulting in running the slim (v1.x) ping process.", processNames = { + "dsfdev_ping", "dsfdev_pong" }) + @Value("${dev.dsf.bpe.ping.max.download.size.bytes:400000000}") + private long maxDownloadSizeBytes; + + @ProcessDocumentation(description = "Sets the upload limit on resource uploads, essentially limiting the amount of data other ping instances are able to download from this instance.", processNames = { + "dsfdev_ping", "dsfdev_pong" }) + @Value("${dev.dsf.bpe.ping.max.upload.size.bytes:400000000}") + private long maxUploadSizeBytes; + + @ProcessDocumentation(description = "Unit to display upload and download speeds in. Eligible values are: \"bps\", \"kbps\", \"Mbps\", \"Gbps\", \"Bps\", \"kBps\", \"MBps\", \"GBps\". If left empty, the process will try to fit the network speed to appropriate units.", processNames = { + "dsfdev_ping", "dsfdev_pong" }) + @Value("${dev.dsf.bpe.ping.network.speed.unit:#{null}}") + private CodeSystem.DsfPingUnits.Code networkSpeedUnit; + + public CodeSystem.DsfPingUnits.Code getNetworkSpeedUnit() + { + return networkSpeedUnit; + } + + public void setNetworkSpeedUnit(CodeSystem.DsfPingUnits.Code networkSpeedUnit) + { + this.networkSpeedUnit = networkSpeedUnit; + } + + public long getMaxDownloadSizeBytes() + { + return maxDownloadSizeBytes; + } + + public void setMaxDownloadSizeBytes(long maxDownloadSizeBytes) + { + this.maxDownloadSizeBytes = maxDownloadSizeBytes; + } + + public long getMaxUploadSizeBytes() + { + return maxUploadSizeBytes; + } + + public void setMaxUploadSizeBytes(long maxUploadSizeBytes) + { + this.maxUploadSizeBytes = maxUploadSizeBytes; + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public PingPongProcessPluginDeploymentStateListener pingPongProcessPluginDeploymentStateListener() + { + return new PingPongProcessPluginDeploymentStateListener(api); + } + @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public SetTargetAndConfigureTimer setTargetAndConfigureTimer() @@ -53,18 +129,25 @@ public SendStartPing sendStartPing() } @Bean - public PingStatusGenerator responseGenerator() + public AggregateErrorMailService aggregateErrorMailServicePing() { - return new PingStatusGenerator(); + return new AggregateErrorMailService(api, sendPingProcessFailedMail); } @Bean - public ErrorMailService errorLogger() + public AggregateErrorMailService aggregateErrorMailServicePong() { - return new ErrorMailService(api, sendPingProcessFailedMail, sendPongProcessFailedMail); + return new AggregateErrorMailService(api, sendPongProcessFailedMail); } + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public SetPongTimeoutDuration setPongTimeoutDuration() + { + return new SetPongTimeoutDuration(api); + } + @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public SelectPingTargets selectPingTargets() @@ -74,9 +157,9 @@ public SelectPingTargets selectPingTargets() @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public SendPing sendPing() + public SendPingMessage sendPing() { - return new SendPing(api); + return new SendPingMessage(api); } @Bean @@ -88,50 +171,197 @@ public SetCorrelationKeyListener setCorrelationKeyListener() @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public LogPong logPong() + public StoreResults savePingResults() + { + return new StoreResults(api, aggregateErrorMailServicePing(), networkSpeedUnit); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public LogPing logPing() { - return new LogPong(api); + return new LogPing(api); } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public LogNoResponse logNoResponse() + public SelectPongTarget selectPongTarget() { - return new LogNoResponse(api); + return new SelectPongTarget(api); } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public LogSendError logSendError() + public SendPongMessage sendPong() { - return new LogSendError(api); + return new SendPongMessage(api, aggregateErrorMailServicePong()); } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public SaveResults savePingResults() + public CheckPingTaskStatus logAndSaveNoResponse() { - return new SaveResults(api, responseGenerator(), errorLogger()); + return new CheckPingTaskStatus(api); } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public LogPing logPing() + public CleanupPongMessage cleanupPongMessage() { - return new LogPing(api); + return new CleanupPongMessage(api); } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public SelectPongTarget selectPongTarget() + public DownloadResourceAndMeasureSpeed downloadResourceAndMeasureSpeed() { - return new SelectPongTarget(api); + return new DownloadResourceAndMeasureSpeed(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public DownloadResourceAndMeasureSpeedInSubProcess downloadResourceAndMeasureSpeedInSubProcess() + { + return new DownloadResourceAndMeasureSpeedInSubProcess(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public Cleanup cleanup() + { + return new Cleanup(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public LogAndSaveAndStoreError logAndSaveAndStoreError() + { + return new LogAndSaveAndStoreError(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public LogAndSaveError logAndSaveError() + { + return new LogAndSaveError(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public StoreUploadSpeed storeDownloadSpeeds() + { + return new StoreUploadSpeed(api, networkSpeedUnit); } @Bean @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) - public SendPong sendPong() + public EstimateCleanupTimerDuration estimateCleanupTimerDuration() { - return new SendPong(api, responseGenerator(), errorLogger()); + return new EstimateCleanupTimerDuration(api); } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public SetDownloadResourceSize setDownloadResourceSize() + { + return new SetDownloadResourceSize(api, maxDownloadSizeBytes); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public SavePong savePong() + { + return new SavePong(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public SetEndpointIdentifier setEndpointIdentifier() + { + return new SetEndpointIdentifier(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public StoreDownloadSpeed storeDownloadSpeed() + { + return new StoreDownloadSpeed(api, networkSpeedUnit); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public dev.dsf.bpe.service.ping.LogAndSaveSendError logAndSaveSendError() + { + return new dev.dsf.bpe.service.ping.LogAndSaveSendError(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public GenerateAndStoreResource generateAndStoreResource() + { + return new GenerateAndStoreResource(api, maxUploadSizeBytes); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public SaveTimeoutError saveTimeoutError() + { + return new SaveTimeoutError(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public StoreErrors storeErrors() + { + return new StoreErrors(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public LogAndSaveUploadErrorPing logAndSaveUploadErrorPing() + { + return new LogAndSaveUploadErrorPing(api); + } + + @Bean + @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) + public LogAndSaveUploadErrorPong logAndSaveUploadErrorPong() + { + return new LogAndSaveUploadErrorPong(api); + } + + @Bean + public dev.dsf.bpe.variables.codesystem.dsfpingstatus.CodeValueSerializer pingStatusCodeSerializer() + { + return new dev.dsf.bpe.variables.codesystem.dsfpingstatus.CodeValueSerializer(); + } + + @Bean + public DurationValueSerializer durationValueSerializer( + @Qualifier(OBJECT_MAPPER_WITH_TIME_MODULE) ObjectMapper objectMapper) + { + return new DurationValueSerializer(objectMapper); + } + + @Bean + public ProcessErrorValueSerializer processErrorValueSerializer() + { + return new ProcessErrorValueSerializer(); + } + + @Bean + public ProcessErrorsValueSerializer processErrorsValueSerializer() + { + return new ProcessErrorsValueSerializer(); + } + + @Bean(name = OBJECT_MAPPER_WITH_TIME_MODULE) + public ObjectMapper objectMapperWithJavaTimeModule() + { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.registerModule(new JavaTimeModule()); + return objectMapper; + } + + private static final String OBJECT_MAPPER_WITH_TIME_MODULE = "objectMapperWithJavaTimeModule"; } diff --git a/src/main/java/dev/dsf/bpe/util/BinaryResourceDownloader.java b/src/main/java/dev/dsf/bpe/util/BinaryResourceDownloader.java new file mode 100644 index 00000000..e6b275f8 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/BinaryResourceDownloader.java @@ -0,0 +1,303 @@ +package dev.dsf.bpe.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.SocketTimeoutException; +import java.time.Duration; +import java.util.Locale; +import java.util.Optional; + +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.HttpHostConnectException; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Task; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.v1.ProcessPluginApi; +import dev.dsf.bpe.v1.variables.Variables; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.WebApplicationException; + +public class BinaryResourceDownloader +{ + private static final Logger logger = LoggerFactory.getLogger(BinaryResourceDownloader.class); + private final String process; + + public BinaryResourceDownloader(String process) + { + this.process = process; + } + + public DownloadResult download(Variables variables, ProcessPluginApi api, Task task) + { + DownloadResult downloadResult; + + long downloadResourceSizeBytes = variables.getLong(ExecutionVariables.downloadResourceSizeBytes.name()); + + Optional optDownloadResourceReference = api.getTaskHelper().getFirstInputParameterValue(task, + CodeSystem.DsfPing.URL, CodeSystem.DsfPing.Code.DOWNLOAD_RESOURCE_REFERENCE.getValue(), + Reference.class); + + if (optDownloadResourceReference.isEmpty()) + { + ProcessError error = new ProcessError(process, + CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_MISSING_REFERENCE, null); + ProcessError errorRemote = new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_MISSING_REFERENCE, null); + logDownloadError("Missing binary reference"); + downloadResult = new DownloadResult(error, errorRemote); + return downloadResult; + } + + Reference downloadResourceReference = optDownloadResourceReference.get(); + IdType downloadResourceReferenceIdType = new IdType(downloadResourceReference.getReference()); + String downloadResourceReferenceId = downloadResourceReferenceIdType.getIdPart(); + String webserviceUrl = downloadResourceReferenceIdType.getBaseUrl(); + try + { + InputStream binaryResourceInputStream = api.getFhirWebserviceClientProvider() + .getWebserviceClient(webserviceUrl) + .readBinary(downloadResourceReferenceId, ConstantsPing.DOWNLOAD_RESOURCE_MIME_TYPE); + + try (binaryResourceInputStream) + { + logger.info("Downloading resource for: '{}'. Requested resource size is {} bytes...", + downloadResourceReference.getReference(), downloadResourceSizeBytes); + long downloadStartTime = System.currentTimeMillis(); + binaryResourceInputStream.skipNBytes(downloadResourceSizeBytes); + long downloadEndTime = System.currentTimeMillis(); + Duration downloadedDuration = Duration.ofMillis(downloadEndTime - downloadStartTime); + downloadResult = new DownloadResult(downloadResourceSizeBytes, downloadedDuration); + logger.info("Finished downloading {} bytes. Took {}", downloadResourceSizeBytes, + downloadedDuration.toString()); + } + catch (IOException e) + { + binaryResourceInputStream.close(); + String errorMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + ProcessError error = new ProcessError(process, + CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_IO_ERROR, null); + ProcessError errorRemote = new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_IO_ERROR, null); + logDownloadError(errorMessage); + downloadResult = new DownloadResult(error, errorRemote); + } + } + catch (WebApplicationException e) + { + String errorMessage = (e.getResponse().getStatusInfo().getStatusCode() + " " + e.getMessage()).trim(); + int statusCode = e.getResponse().getStatus(); + ProcessError error; + ProcessError errorRemote; + + switch (statusCode) + { + case 401: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + case 403: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + case 500: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + case 502: + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + errorRemote = new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP); + break; + default: + error = new ProcessError(process, + CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_HTTP_UNEXPECTED, null); + errorRemote = new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_HTTP_UNEXPECTED, null); + break; + } + logDownloadError(errorMessage); + downloadResult = new DownloadResult(error, errorRemote); + } + catch (ProcessingException e) + { + if (e.getCause() instanceof SocketTimeoutException socketTimeoutException) + { + String errorMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + ProcessError error = toProcessErrorLocal(socketTimeoutException, process); + ProcessError errorRemote = toProcessErrorRemote(socketTimeoutException, process); + logDownloadError(errorMessage); + downloadResult = new DownloadResult(error, errorRemote); + } + else if (e.getCause() instanceof ConnectTimeoutException) + { + String errorMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + ProcessError error = toProcessErrorLocalConnectTimeout(process); + ProcessError errorRemote = toProcessErrorRemoteConnectTimeout(process); + logDownloadError(errorMessage); + downloadResult = new DownloadResult(error, errorRemote); + } + else if (e.getCause() instanceof HttpHostConnectException) + { + String errorMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + ProcessError error = toProcessErrorLocalHttpHostConnect(process); + ProcessError errorRemote = toProcessErrorRemoteHttpHostConnect(process); + logDownloadError(errorMessage); + downloadResult = new DownloadResult(error, errorRemote); + } + else + { + String errorMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + ProcessError error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, null); + ProcessError errorRemote = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_UNKNOWN, + null); + logger.error("Unexpected error: {}", errorMessage); + downloadResult = new DownloadResult(error, errorRemote); + } + } + catch (IOException e) + { + String errorMessage = e.getMessage() != null ? e.getMessage() : e.getClass().getSimpleName(); + ProcessError error = new ProcessError(process, + CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_IO_ERROR, + ConstantsPing.POTENTIAL_FIX_URL_READ_TIMEOUT); + ProcessError errorRemote = new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_IO_ERROR, + ConstantsPing.POTENTIAL_FIX_URL_READ_TIMEOUT); + logDownloadError(errorMessage); + downloadResult = new DownloadResult(error, errorRemote); + } + return downloadResult; + } + + private ProcessError toProcessErrorLocal(SocketTimeoutException timeoutException, String process) + { + ProcessError error; + String message = timeoutException.getMessage().toLowerCase(Locale.ROOT); + if (message.contains("connect")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + else if (message.contains("read")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_TIMEOUT_READ, + ConstantsPing.POTENTIAL_FIX_URL_READ_TIMEOUT); + } + else + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, null); + logger.error("Unexpected error: {}", timeoutException.getMessage()); + } + return error; + } + + private ProcessError toProcessErrorRemote(SocketTimeoutException timeoutException, String process) + { + ProcessError error; + String message = timeoutException.getMessage().toLowerCase(Locale.ROOT); + if (message.contains("connect")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + else if (message.contains("read")) + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_TIMEOUT_READ, + ConstantsPing.POTENTIAL_FIX_URL_READ_TIMEOUT); + } + else + { + error = new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_UNKNOWN, null); + logger.error("Unexpected error: {}", timeoutException.getMessage()); + } + return error; + } + + private ProcessError toProcessErrorLocalHttpHostConnect(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_HTTP_HOST_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_REFUSED); + } + + private ProcessError toProcessErrorRemoteHttpHostConnect(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_HTTP_HOST_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_REFUSED); + } + + private ProcessError toProcessErrorLocalConnectTimeout(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + + private ProcessError toProcessErrorRemoteConnectTimeout(String process) + { + return new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + } + + private void logDownloadError(String errorMessage) + { + logger.error("Encountered an error while downloading resource: {}", errorMessage); + } + + public static class DownloadResult + { + private final long downloadedBytes; + private final Duration downloadedDuration; + private final ErrorTuple errorTuple; + + public DownloadResult(long downloadedBytes, Duration downloadedDuration) + { + this.downloadedBytes = downloadedBytes; + this.downloadedDuration = downloadedDuration; + errorTuple = null; + } + + public DownloadResult(ProcessError error, ProcessError errorRemote) + { + downloadedBytes = 0; + downloadedDuration = Duration.ZERO; + this.errorTuple = new ErrorTuple(error, errorRemote); + } + + public long getDownloadedBytes() + { + return downloadedBytes; + } + + public Duration getDownloadedDuration() + { + return downloadedDuration; + } + + public ErrorTuple getErrorTuple() + { + return errorTuple; + } + + public record ErrorTuple(ProcessError errorLocal, ProcessError errorRemote) + { + } + + } +} diff --git a/src/main/java/dev/dsf/bpe/util/ErrorListUtils.java b/src/main/java/dev/dsf/bpe/util/ErrorListUtils.java new file mode 100644 index 00000000..0df63dc7 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/ErrorListUtils.java @@ -0,0 +1,168 @@ +package dev.dsf.bpe.util; + +import org.camunda.bpm.engine.delegate.DelegateExecution; + +import dev.dsf.bpe.ExecutionVariables; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.ProcessErrors; +import dev.dsf.bpe.variables.process_errors.ProcessErrorsValueImpl; + +public class ErrorListUtils +{ + public static void addAll(ProcessErrors errors, DelegateExecution execution) + { + ProcessErrors errorList = getErrorList(execution); + if (errors != null) + { + errorList.addAll(errors); + saveErrorList(errorList, execution, null); + } + } + + public static void addAll(ProcessErrors errors, DelegateExecution execution, String correlationKey) + { + ProcessErrors errorList = correlationKey != null ? getErrorList(execution, correlationKey) + : getErrorList(execution); + if (errors != null) + { + errorList.addAll(errors); + saveErrorList(errorList, execution, correlationKey); + } + } + + public static void addAllRemote(ProcessErrors errors, DelegateExecution execution) + { + ProcessErrors errorList = getErrorListRemote(execution); + if (errors != null) + { + errorList.addAll(errors); + saveErrorListRemote(errorList, execution, null); + } + } + + public static void addAllRemote(ProcessErrors errors, DelegateExecution execution, String correlationKey) + { + ProcessErrors errorList = correlationKey != null ? getErrorListRemote(execution, correlationKey) + : getErrorListRemote(execution); + if (errors != null) + { + errorList.addAll(errors); + saveErrorListRemote(errorList, execution, correlationKey); + } + } + + public static void add(ProcessError error, DelegateExecution execution) + { + add(error, execution, null); + } + + public static void add(ProcessError error, DelegateExecution execution, String correlationKey) + { + if (correlationKey != null) + { + add(error, ExecutionVariables.errors.correlatedValue(correlationKey), execution); + } + else + { + add(error, ExecutionVariables.errors.name(), execution); + } + } + + public static void addRemote(ProcessError error, DelegateExecution execution) + { + addRemote(error, execution, null); + } + + public static void addRemote(ProcessError error, DelegateExecution execution, String correlationKey) + { + if (correlationKey != null) + { + add(error, ExecutionVariables.errorsRemote.correlatedValue(correlationKey), execution); + } + else + { + add(error, ExecutionVariables.errorsRemote.name(), execution); + } + } + + public static ProcessErrors getErrorList(DelegateExecution execution) + { + return getErrorList(execution, null); + } + + public static ProcessErrors getErrorList(DelegateExecution execution, String correlationKey) + { + if (correlationKey != null) + { + return getErrorList(ExecutionVariables.errors.correlatedValue(correlationKey), execution); + } + else + { + return getErrorList(ExecutionVariables.errors.name(), execution); + } + } + + public static ProcessErrors getErrorListRemote(DelegateExecution execution) + { + return getErrorListRemote(execution, null); + } + + public static ProcessErrors getErrorListRemote(DelegateExecution execution, String correlationKey) + { + if (correlationKey != null) + { + return getErrorList(ExecutionVariables.errorsRemote.correlatedValue(correlationKey), execution); + } + else + { + return getErrorList(ExecutionVariables.errorsRemote.name(), execution); + } + } + + public static ProcessErrors getErrorList(String variableName, DelegateExecution execution) + { + ProcessErrors errors = (ProcessErrors) execution.getVariable(variableName); + if (errors == null) + { + errors = new ProcessErrors(); + saveErrorList(errors, variableName, execution); + } + return errors; + } + + public static void add(ProcessError error, String variableName, DelegateExecution execution) + { + ProcessErrors errors = getErrorList(variableName, execution); + errors.add(error); + saveErrorList(errors, variableName, execution); + } + + private static void saveErrorList(ProcessErrors errors, DelegateExecution execution, String correlationKey) + { + if (correlationKey != null) + { + saveErrorList(errors, ExecutionVariables.errors.correlatedValue(correlationKey), execution); + } + else + { + saveErrorList(errors, ExecutionVariables.errors.name(), execution); + } + } + + private static void saveErrorListRemote(ProcessErrors errors, DelegateExecution execution, String correlationKey) + { + if (correlationKey != null) + { + saveErrorList(errors, ExecutionVariables.errorsRemote.correlatedValue(correlationKey), execution); + } + else + { + saveErrorList(errors, ExecutionVariables.errorsRemote.name(), execution); + } + } + + private static void saveErrorList(ProcessErrors errors, String variableName, DelegateExecution execution) + { + execution.setVariable(variableName, new ProcessErrorsValueImpl(errors)); + } +} diff --git a/src/main/java/dev/dsf/bpe/util/PingStatusGenerator.java b/src/main/java/dev/dsf/bpe/util/PingStatusGenerator.java deleted file mode 100644 index 142ded7d..00000000 --- a/src/main/java/dev/dsf/bpe/util/PingStatusGenerator.java +++ /dev/null @@ -1,57 +0,0 @@ -package dev.dsf.bpe.util; - -import org.hl7.fhir.r4.model.Coding; -import org.hl7.fhir.r4.model.Extension; -import org.hl7.fhir.r4.model.StringType; -import org.hl7.fhir.r4.model.Task.TaskOutputComponent; - -import dev.dsf.bpe.ConstantsPing; -import dev.dsf.bpe.v1.constants.NamingSystems.EndpointIdentifier; -import dev.dsf.bpe.v1.constants.NamingSystems.OrganizationIdentifier; -import dev.dsf.bpe.v1.variables.Target; - -public class PingStatusGenerator -{ - public TaskOutputComponent createPingStatusOutput(Target target, String statusCode) - { - return createPingStatusOutput(target, statusCode, null); - } - - public TaskOutputComponent createPingStatusOutput(Target target, String statusCode, String errorMessage) - { - return createStatusOutput(target, ConstantsPing.CODESYSTEM_DSF_PING_VALUE_PING_STATUS, statusCode, - errorMessage); - } - - public TaskOutputComponent createPongStatusOutput(Target target, String statusCode) - { - return createPongStatusOutput(target, statusCode, null); - } - - public TaskOutputComponent createPongStatusOutput(Target target, String statusCode, String errorMessage) - { - return createStatusOutput(target, ConstantsPing.CODESYSTEM_DSF_PING_VALUE_PONG_STATUS, statusCode, - errorMessage); - } - - private TaskOutputComponent createStatusOutput(Target target, String outputParameter, String statusCode, - String errorMessage) - { - TaskOutputComponent output = new TaskOutputComponent(); - output.setValue(new Coding().setSystem(ConstantsPing.CODESYSTEM_DSF_PING_STATUS).setCode(statusCode)); - output.getType().addCoding().setSystem(ConstantsPing.CODESYSTEM_DSF_PING).setCode(outputParameter); - - Extension extension = output.addExtension(); - extension.setUrl(ConstantsPing.EXTENSION_URL_PING_STATUS); - extension.addExtension(ConstantsPing.EXTENSION_URL_CORRELATION_KEY, new StringType(target.getCorrelationKey())); - extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ORGANIZATION_IDENTIFIER) - .setValue(OrganizationIdentifier.withValue(target.getOrganizationIdentifierValue())); - extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ENDPOINT_IDENTIFIER) - .setValue(EndpointIdentifier.withValue(target.getEndpointIdentifierValue())); - if (errorMessage != null) - extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ERROR_MESSAGE) - .setValue(new StringType(errorMessage)); - - return output; - } -} diff --git a/src/main/java/dev/dsf/bpe/util/logging/TaskIdPrefixer.java b/src/main/java/dev/dsf/bpe/util/logging/TaskIdPrefixer.java new file mode 100644 index 00000000..abf9a659 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/logging/TaskIdPrefixer.java @@ -0,0 +1,16 @@ +package dev.dsf.bpe.util.logging; + +import org.hl7.fhir.r4.model.Task; + +public class TaskIdPrefixer +{ + private TaskIdPrefixer() + { + + } + + public static String prefixTaskId(Task task, String message) + { + return "Process for task with id " + task.getIdElement().getValue() + ": " + message; + } +} diff --git a/src/main/java/dev/dsf/bpe/util/task/SendTaskErrorConverter.java b/src/main/java/dev/dsf/bpe/util/task/SendTaskErrorConverter.java new file mode 100644 index 00000000..76b43560 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/task/SendTaskErrorConverter.java @@ -0,0 +1,520 @@ +package dev.dsf.bpe.util.task; + +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; +import java.util.Locale; +import java.util.Objects; + +import javax.net.ssl.SSLHandshakeException; + +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.HttpHostConnectException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ProcessError; +import jakarta.ws.rs.ProcessingException; +import jakarta.ws.rs.WebApplicationException; + +public final class SendTaskErrorConverter +{ + private static final Logger logger = LoggerFactory.getLogger(SendTaskErrorConverter.class); + + private SendTaskErrorConverter() + { + } + + private enum ErrorType + { + LOCAL, + REMOTE + } + + public record ProcessErrorWithStatusCode(ProcessError error, CodeSystem.DsfPingStatus.Code statusCode) + { + } + + public static ProcessErrorWithStatusCode convertLocal(Exception exception, boolean messageWithReference, + String process) + { + return convert(exception, ErrorType.LOCAL, messageWithReference, process); + } + + private static ProcessErrorWithStatusCode convert(Exception exception, ErrorType errorType, + boolean messageWithReference, String process) + { + if (exception instanceof WebApplicationException e) + { + return convertWebApplicationException(e, errorType, messageWithReference, process); + } + else if (exception instanceof SSLHandshakeException) + { + return convertSSLHandshakeException(errorType, process); + } + else if (exception instanceof ConnectTimeoutException) + { + return convertConnectTimeoutException(errorType, process); + } + else if (exception instanceof HttpHostConnectException) + { + return convertHttpHostConnectException(errorType, process); + } + else if (exception instanceof SocketTimeoutException socketTimeoutException) + { + return convertSocketTimeoutException(errorType, process, socketTimeoutException); + } + else if (exception instanceof ProcessingException e) + { + SSLHandshakeException sslHandshakeException = getExpectedCauseInstanceFromStack(SSLHandshakeException.class, + e); + if (sslHandshakeException != null) + { + return convertSSLHandshakeException(errorType, process); + } + + ConnectTimeoutException connectTimeoutException = getExpectedCauseInstanceFromStack( + ConnectTimeoutException.class, e); + if (connectTimeoutException != null) + { + return convertConnectTimeoutException(errorType, process); + } + + UnknownHostException unknownHostException = getExpectedCauseInstanceFromStack(UnknownHostException.class, + e); + if (unknownHostException != null) + { + return convertUnknownHostException(errorType, process); + } + + HttpHostConnectException httpHostConnectException = getExpectedCauseInstanceFromStack( + HttpHostConnectException.class, e); + if (httpHostConnectException != null) + { + return convertHttpHostConnectException(errorType, process); + } + + SocketTimeoutException socketTimeoutException = getExpectedCauseInstanceFromStack( + SocketTimeoutException.class, e); + if (socketTimeoutException != null) + { + return convertSocketTimeoutException(errorType, process, socketTimeoutException); + } + + return convertExceptionFallback(exception, errorType, process); + } + else + { + return convertExceptionFallback(exception, errorType, process); + } + } + + private static ProcessErrorWithStatusCode convertSocketTimeoutException(ErrorType errorType, String process, + SocketTimeoutException socketTimeoutException) + { + ProcessError error; + String message = socketTimeoutException.getMessage().toLowerCase(Locale.ROOT); + if (message.contains("connect")) + { + error = switch (errorType) + { + case LOCAL -> new ProcessError(process, + CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + case REMOTE -> new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_TIMEOUT_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + }; + return new ProcessErrorWithStatusCode(error, CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + } + else if (message.contains("read")) + { + error = switch (errorType) + { + case LOCAL -> new ProcessError(process, + CodeSystem.DsfPingError.Concept.LOCAL_BINARY_DOWNLOAD_TIMEOUT_READ, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + case REMOTE -> new ProcessError(process, + CodeSystem.DsfPingError.Concept.REMOTE_BINARY_DOWNLOAD_TIMEOUT_READ, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + }; + return new ProcessErrorWithStatusCode(error, CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + } + else + { + error = switch (errorType) + { + case LOCAL -> new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + case REMOTE -> new ProcessError(process, CodeSystem.DsfPingError.Concept.REMOTE_UNKNOWN, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT); + }; + logger.error("Unexpected error: {}", socketTimeoutException.getMessage()); + return new ProcessErrorWithStatusCode(error, CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + } + + } + + private static ProcessErrorWithStatusCode convertSSLHandshakeException(ErrorType errorType, String process) + { + if (ConstantsPing.PROCESS_NAME_PING.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_SSL_HANDSHAKE, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_SSL), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_SSL_HANDSHAKE, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_SSL), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_SSL_HANDSHAKE, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_SSL), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_SSL_HANDSHAKE, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_SSL), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else + { + throw new IllegalArgumentException("Unknown process: " + process); + } + } + + private static ProcessErrorWithStatusCode convertExceptionFallback(Exception e, ErrorType errorType, String process) + { + logger.warn("No fitting converter found for exception {}: {}", e.getClass().getName(), e.getMessage()); + if (ConstantsPing.PROCESS_NAME_PING.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode(new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, null), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.REMOTE_UNKNOWN, null), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode(new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, null), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.REMOTE_UNKNOWN, null), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else + { + throw new IllegalArgumentException("Unknown process: " + process); + } + } + + private static ProcessErrorWithStatusCode convertConnectTimeoutException(ErrorType errorType, String process) + { + if (ConstantsPing.PROCESS_NAME_PING.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_CONNECT_TIMEOUT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_CONNECT_TIMEOUT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_CONNECT_TIMEOUT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_CONNECT_TIMEOUT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_TIMEOUT), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else + { + throw new IllegalArgumentException("Unknown process: " + process); + } + } + + private static ProcessErrorWithStatusCode convertWebApplicationException(WebApplicationException e, + ErrorType errorType, boolean messageWithReference, String process) + { + int statusCode = e.getResponse().getStatus(); + if (ConstantsPing.PROCESS_NAME_PING.equals(process)) + { + return switch (errorType) + { + case LOCAL -> switch (statusCode) + { + case 401 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + case 403 -> + { + if (messageWithReference) + { + yield new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_REFERENCE_MESSAGE_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + } + else + { + yield new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + } + } + case 500 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case 502 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + default -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_UNEXPECTED, null), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + case REMOTE -> switch (statusCode) + { + case 401 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + case 403 -> + { + if (messageWithReference) + { + yield new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_REFERENCE_MESSAGE_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + } + else + { + yield new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + } + } + case 500 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case 502 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + default -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_UNEXPECTED, null), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + }; + } + else if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + return switch (errorType) + { + case LOCAL -> switch (statusCode) + { + case 401 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + case 403 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + case 500 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case 502 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + default -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_UNEXPECTED, null), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + case REMOTE -> switch (statusCode) + { + case 401 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_401, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + case 403 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_403, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_ALLOWED); + case 500 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_500, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case 502 -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_502, + ConstantsPing.POTENTIAL_FIX_URL_ERROR_HTTP), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + default -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_UNEXPECTED, null), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + }; + } + else + { + throw new IllegalArgumentException("Unknown process: " + process); + } + } + + private static ProcessErrorWithStatusCode convertHttpHostConnectException(ErrorType errorType, String process) + { + if (ConstantsPing.PROCESS_NAME_PING.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_HOST_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_REFUSED), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_HOST_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_REFUSED), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_HOST_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_REFUSED), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_HTTP_HOST_CONNECT, + ConstantsPing.POTENTIAL_FIX_URL_CONNECTION_REFUSED), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else + { + throw new IllegalArgumentException("Unknown process: " + process); + } + } + + private static ProcessErrorWithStatusCode convertUnknownHostException(ErrorType errorType, String process) + { + if (ConstantsPing.PROCESS_NAME_PING.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_UNKNOWN_HOST, + ConstantsPing.POTENTIAL_FIX_URL_UNKNOWN_HOST), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PING, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_UNKNOWN_HOST, + ConstantsPing.POTENTIAL_FIX_URL_UNKNOWN_HOST), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else if (ConstantsPing.PROCESS_NAME_PONG.equals(process)) + { + return switch (errorType) + { + case LOCAL -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.SEND_MESSAGE_UNKNOWN_HOST, + ConstantsPing.POTENTIAL_FIX_URL_UNKNOWN_HOST), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + case REMOTE -> new ProcessErrorWithStatusCode( + new ProcessError(ConstantsPing.PROCESS_NAME_PONG, + CodeSystem.DsfPingError.Concept.RECEIVE_MESSAGE_UNKNOWN_HOST, + ConstantsPing.POTENTIAL_FIX_URL_UNKNOWN_HOST), + CodeSystem.DsfPingStatus.Code.NOT_REACHABLE); + }; + } + else + { + throw new IllegalArgumentException("Unknown process: " + process); + } + } + + private static T getExpectedCauseInstanceFromStack(Class expectedCause, Throwable e) + { + if (Objects.isNull(e)) + return null; + if (expectedCause.isInstance(e)) + return expectedCause.cast(e); + return getExpectedCauseInstanceFromStack(expectedCause, e.getCause()); + } +} diff --git a/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadResourceReferenceGenerator.java b/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadResourceReferenceGenerator.java new file mode 100644 index 00000000..a8e2a09f --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadResourceReferenceGenerator.java @@ -0,0 +1,23 @@ +package dev.dsf.bpe.util.task.input.generator; + +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.model.Task; + +import dev.dsf.bpe.CodeSystem; + +public final class DownloadResourceReferenceGenerator +{ + private DownloadResourceReferenceGenerator() + { + } + + public static Task.ParameterComponent create(String uri) + { + Reference reference = new Reference(uri); + reference.setType("Binary"); + Task.ParameterComponent param = new Task.ParameterComponent(); + param.setValue(reference).getType() + .addCoding(CodeSystem.DsfPing.fromCode(CodeSystem.DsfPing.Code.DOWNLOAD_RESOURCE_REFERENCE)); + return param; + } +} diff --git a/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadResourceSizeGenerator.java b/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadResourceSizeGenerator.java new file mode 100644 index 00000000..53429582 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadResourceSizeGenerator.java @@ -0,0 +1,21 @@ +package dev.dsf.bpe.util.task.input.generator; + +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.Task; + +import dev.dsf.bpe.CodeSystem; + +public final class DownloadResourceSizeGenerator +{ + private DownloadResourceSizeGenerator() + { + } + + public static Task.ParameterComponent create(long sizeBytes) + { + Task.ParameterComponent param = new Task.ParameterComponent(); + param.setValue(new DecimalType(sizeBytes)).getType() + .addCoding(CodeSystem.DsfPing.fromCode(CodeSystem.DsfPing.Code.DOWNLOAD_RESOURCE_SIZE_BYTES)); + return param; + } +} diff --git a/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadedBytesGenerator.java b/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadedBytesGenerator.java new file mode 100644 index 00000000..e8f22422 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadedBytesGenerator.java @@ -0,0 +1,23 @@ +package dev.dsf.bpe.util.task.input.generator; + +import org.hl7.fhir.r4.model.DecimalType; +import org.hl7.fhir.r4.model.Task; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.PingProcessPluginDefinition; + +public final class DownloadedBytesGenerator +{ + private DownloadedBytesGenerator() + { + } + + public static Task.ParameterComponent create(long bytes) + { + Task.ParameterComponent param = new Task.ParameterComponent(); + param.setValue(new DecimalType(bytes)).getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.DOWNLOADED_BYTES.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); + return param; + } +} diff --git a/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadedDurationGenerator.java b/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadedDurationGenerator.java new file mode 100644 index 00000000..b24288f5 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/task/input/generator/DownloadedDurationGenerator.java @@ -0,0 +1,28 @@ +package dev.dsf.bpe.util.task.input.generator; + +import java.time.Duration; + +import org.hl7.fhir.r4.model.Task; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.PingProcessPluginDefinition; + +public final class DownloadedDurationGenerator +{ + private static final String CODESYSTEM_UCUM = "http://unitsofmeasure.org"; + private static final String CODESYSTEM_UCUM_CODE_MILLISECONDS = "ms"; + + private DownloadedDurationGenerator() + { + } + + public static Task.ParameterComponent create(Duration duration) + { + Task.ParameterComponent param = new Task.ParameterComponent(); + param.setValue(new org.hl7.fhir.r4.model.Duration().setValue(duration.toMillis()).setSystem(CODESYSTEM_UCUM) + .setCode(CODESYSTEM_UCUM_CODE_MILLISECONDS)).getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.DOWNLOADED_DURATION_MILLIS.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); + return param; + } +} diff --git a/src/main/java/dev/dsf/bpe/util/task/input/generator/ErrorInputComponentGenerator.java b/src/main/java/dev/dsf/bpe/util/task/input/generator/ErrorInputComponentGenerator.java new file mode 100644 index 00000000..eb821935 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/task/input/generator/ErrorInputComponentGenerator.java @@ -0,0 +1,41 @@ +package dev.dsf.bpe.util.task.input.generator; + +import java.util.List; +import java.util.stream.Collectors; + +import org.hl7.fhir.r4.model.CodeType; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.Task; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ProcessError; + +public final class ErrorInputComponentGenerator +{ + private ErrorInputComponentGenerator() + { + } + + public static List create(List errors) + { + if (errors == null || errors.isEmpty()) + return List.of(); + return errors.stream().map(ErrorInputComponentGenerator::create).collect(Collectors.toList()); + } + + public static Task.ParameterComponent create(ProcessError error) + { + Task.ParameterComponent param = new Task.ParameterComponent(); + + param.getType().addCoding(CodeSystem.DsfPing.fromCode(CodeSystem.DsfPing.Code.ERROR)); + param.addExtension(ProcessError.toExtension(error)); + Extension dataAbsentReason = new Extension() + .setUrl("http://hl7.org/fhir/StructureDefinition/data-absent-reason") + .setValue(new CodeType("not-applicable")); + param.setValue(new StringType()); + param.getValue().addExtension(dataAbsentReason); + + return param; + } +} diff --git a/src/main/java/dev/dsf/bpe/util/task/output/generator/PingStatusGenerator.java b/src/main/java/dev/dsf/bpe/util/task/output/generator/PingStatusGenerator.java new file mode 100644 index 00000000..1e004ab4 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/util/task/output/generator/PingStatusGenerator.java @@ -0,0 +1,710 @@ +package dev.dsf.bpe.util.task.output.generator; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Quantity; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.Task; +import org.hl7.fhir.r4.model.Task.TaskOutputComponent; +import org.hl7.fhir.r4.model.Type; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.PingProcessPluginDefinition; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.v1.constants.NamingSystems.EndpointIdentifier; +import dev.dsf.bpe.v1.constants.NamingSystems.OrganizationIdentifier; +import dev.dsf.bpe.v1.variables.Target; + +public final class PingStatusGenerator +{ + private static final String CODESYSTEM_UCUM = "http://unitsofmeasure.org"; + + private PingStatusGenerator() + { + } + + public static Task updatePongStatusOutput(Task task, List errors) + { + List pongStatusOutputs = getOutputsByExtensionUrlAndCodes(task, + CodeSystem.DsfPing.Code.PONG_STATUS.getValue()); + if (pongStatusOutputs.isEmpty()) + { + task.addOutput(updateStatusOutput(new TaskOutputComponent(), errors)); + } + else + { + if (pongStatusOutputs.size() == 1) + { + updateStatusOutput(pongStatusOutputs.get(0), errors); + } + else + { + throw new RuntimeException("There is more than one pong status output for task " + task.getId()); + } + } + + return task; + } + + public static TaskOutputComponent updateStatusOutput(TaskOutputComponent output, List errors) + { + if (output != null) + { + Extension pingStatusExtension = getOrCreatePingStatusExtension(output); + List errorExtensions = pingStatusExtension.getExtension().stream() + .filter(extension -> ConstantsPing.EXTENSION_URL_ERRORS.equals(extension.getUrl())).toList(); + if (errorExtensions.isEmpty()) + { + addErrors(output, errors); + } + else + { + updateErrors(output, errors); + } + } + + return output; + } + + public static Task updatePongStatusOutput(Task task, CodeSystem.DsfPingStatus.Code statusCode) + { + List pongStatusOutputs = getOutputsByExtensionUrlAndCodes(task, + CodeSystem.DsfPing.Code.PONG_STATUS.getValue()); + if (pongStatusOutputs.isEmpty()) + { + task.addOutput(updatePongStatusOutput(new TaskOutputComponent(), statusCode)); + } + else + { + if (pongStatusOutputs.size() == 1) + { + updatePongStatusOutput(pongStatusOutputs.get(0), statusCode); + } + else + { + throw new RuntimeException("There is more than one pong status output for task " + task.getId()); + } + } + + return task; + } + + private static TaskOutputComponent updatePongStatusOutput(TaskOutputComponent outputComponent, + CodeSystem.DsfPingStatus.Code statusCode) + { + if (hasStatusCodeSet(outputComponent)) + { + updateStatus(outputComponent, CodeSystem.DsfPing.Code.PONG_STATUS.getValue(), statusCode); + } + else + { + addStatus(outputComponent, CodeSystem.DsfPing.Code.PONG_STATUS, statusCode); + } + + return outputComponent; + } + + public static Task updatePongStatusOutput(Task task, Target target) + { + List outputs = getOutputsByExtensionUrlAndCodes(task, + CodeSystem.DsfPing.Code.PONG_STATUS.getValue()); + if (outputs.isEmpty()) + { + task.addOutput(updateStatusOutput(new TaskOutputComponent(), target)); + } + else + { + if (outputs.size() == 1) + { + updateStatusOutput(outputs.get(0), target); + } + else + { + throw new RuntimeException("There is more than one pong status output for task " + task.getId()); + } + } + + return task; + } + + private static TaskOutputComponent updateStatusOutput(TaskOutputComponent outputComponent, Target target) + { + if (hasTargetSet(outputComponent)) + { + updateTarget(outputComponent, target); + } + else + { + addTarget(outputComponent, target); + } + return outputComponent; + } + + public static Task updatePongStatusOutputDownloadSpeed(Task task, BigDecimal downloadSpeed, + CodeSystem.DsfPingUnits.Code networkSpeedUnit) + { + List outputs = getOutputsByExtensionUrlAndCodes(task, + CodeSystem.DsfPing.Code.PONG_STATUS.getValue()); + if (outputs.isEmpty()) + { + task.addOutput(updateStatusOutputDownloadSpeed(new TaskOutputComponent(), downloadSpeed, networkSpeedUnit)); + } + else + { + if (outputs.size() == 1) + { + updateStatusOutputDownloadSpeed(outputs.get(0), downloadSpeed, networkSpeedUnit); + } + else + { + throw new RuntimeException("There is more than one ping/pong status output for task " + task.getId()); + } + } + + return task; + } + + private static TaskOutputComponent updateStatusOutputDownloadSpeed(TaskOutputComponent outputComponent, + BigDecimal downloadSpeed, CodeSystem.DsfPingUnits.Code networkSpeedUnit) + { + if (hasDownloadSpeedSet(outputComponent)) + { + updateDownloadSpeed(outputComponent, downloadSpeed, + networkSpeedUnit != null ? networkSpeedUnit.name() : null, CODESYSTEM_UCUM, + networkSpeedUnit != null ? networkSpeedUnit.toUcum() : null); + } + else + { + addDownloadSpeed(outputComponent, downloadSpeed, networkSpeedUnit != null ? networkSpeedUnit.name() : null, + CODESYSTEM_UCUM, networkSpeedUnit != null ? networkSpeedUnit.toUcum() : null); + } + + return outputComponent; + } + + public static Task updatePongStatusOutputUploadSpeed(Task task, BigDecimal uploadSpeed, + CodeSystem.DsfPingUnits.Code networkSpeedUnit) + { + List outputs = getOutputsByExtensionUrlAndCodes(task, + CodeSystem.DsfPing.Code.PONG_STATUS.getValue()); + if (outputs.isEmpty()) + { + task.addOutput(updateStatusOutputUploadSpeed(new TaskOutputComponent(), uploadSpeed, networkSpeedUnit)); + } + else + { + if (outputs.size() == 1) + { + updateStatusOutputUploadSpeed(outputs.get(0), uploadSpeed, networkSpeedUnit); + } + else + { + throw new RuntimeException("There is more than one ping/pong status output for task " + task.getId()); + } + } + + return task; + } + + private static TaskOutputComponent updateStatusOutputUploadSpeed(TaskOutputComponent outputComponent, + BigDecimal uploadSpeed, CodeSystem.DsfPingUnits.Code networkSpeedUnit) + { + if (hasDownloadSpeedSet(outputComponent)) + { + updateUploadSpeed(outputComponent, uploadSpeed, networkSpeedUnit != null ? networkSpeedUnit.name() : null, + CODESYSTEM_UCUM, networkSpeedUnit != null ? networkSpeedUnit.toUcum() : null); + } + else + { + addUploadSpeed(outputComponent, uploadSpeed, networkSpeedUnit != null ? networkSpeedUnit.name() : null, + CODESYSTEM_UCUM, networkSpeedUnit != null ? networkSpeedUnit.toUcum() : null); + } + + return outputComponent; + } + + private static boolean hasTargetSet(TaskOutputComponent outputComponent) + { + List correlationKeyExtensions = outputComponent + .getExtensionsByUrl(ConstantsPing.EXTENSION_URL_CORRELATION_KEY); + List organizationIdentifierExtensions = outputComponent + .getExtensionsByUrl(ConstantsPing.EXTENSION_URL_ORGANIZATION_IDENTIFIER); + List endpointIdentifierExtensions = outputComponent + .getExtensionsByUrl(ConstantsPing.EXTENSION_URL_ENDPOINT_IDENTIFIER); + return !correlationKeyExtensions.isEmpty() || !organizationIdentifierExtensions.isEmpty() + || !endpointIdentifierExtensions.isEmpty(); + } + + private static boolean hasStatusCodeSet(TaskOutputComponent outputComponent) + { + Type valueType = outputComponent.getValue(); + List outputTypeCodings = outputComponent.getType().getCoding(); + + return (valueType instanceof Coding coding && CodeSystem.DsfPingStatus.URL.equals(coding.getSystem())) + || outputTypeCodings.stream().anyMatch(coding -> CodeSystem.DsfPing.URL.equals(coding.getSystem())); + } + + private static boolean hasDownloadSpeedSet(TaskOutputComponent outputComponent) + { + Extension extension = getOrCreatePingStatusExtension(outputComponent); + Extension downloadSpeedExtension = extension.getExtensionByUrl(ConstantsPing.EXTENSION_URL_DOWNLOAD_SPEED); + + return downloadSpeedExtension != null; + } + + public static TaskOutputComponent createPingStatusOutput(Target target, CodeSystem.DsfPingStatus.Code statusCode, + List errors) + { + return createStatusOutput(target, CodeSystem.DsfPing.Code.PING_STATUS, statusCode, errors, null, null, null, + null, null, null, null, null); + } + + public static TaskOutputComponent createPingStatusOutput(Target target, CodeSystem.DsfPingStatus.Code statusCode, + List errors, BigDecimal downloadSpeed, CodeSystem.DsfPingUnits.Code downloadUnit, + BigDecimal uploadSpeed, CodeSystem.DsfPingUnits.Code uploadUnit) + { + return createStatusOutput(target, CodeSystem.DsfPing.Code.PING_STATUS, statusCode, errors, downloadSpeed, + downloadUnit != null ? downloadUnit.name() : null, CODESYSTEM_UCUM, + downloadUnit != null ? downloadUnit.toUcum() : null, uploadSpeed, + uploadUnit != null ? uploadUnit.name() : null, CODESYSTEM_UCUM, + uploadUnit != null ? uploadUnit.toUcum() : null); + } + + public static TaskOutputComponent createPongStatusOutput(Target target, CodeSystem.DsfPingStatus.Code statusCode, + List errors) + { + return createStatusOutput(target, CodeSystem.DsfPing.Code.PONG_STATUS, statusCode, errors, null, null, null, + null, null, null, null, null); + } + + private static TaskOutputComponent createStatusOutput(Target target, CodeSystem.DsfPing.Code outputParameter, + CodeSystem.DsfPingStatus.Code statusCode, List errors, BigDecimal downloadSpeed, + String downloadUnit, String downloadUnitSystem, String downloadUnitCode, BigDecimal uploadSpeed, + String uploadUnit, String uploadUnitSystem, String uploadUnitCode) + { + TaskOutputComponent output = new TaskOutputComponent(); + addStatus(output, outputParameter, statusCode); + addTarget(output, target); + addErrors(output, errors); + addNetworkSpeed(output, downloadSpeed, downloadUnit, downloadUnitSystem, downloadUnitCode, uploadSpeed, + uploadUnit, uploadUnitSystem, uploadUnitCode); + + return output; + } + + private static TaskOutputComponent addStatus(TaskOutputComponent outputComponent, + CodeSystem.DsfPing.Code outputParameter, CodeSystem.DsfPingStatus.Code statusCode) + { + if (outputParameter != null && statusCode != null) + { + outputComponent.setValue(CodeSystem.DsfPingStatus.fromCode(statusCode)); + outputComponent.getType().addCoding(CodeSystem.DsfPing.fromCode(outputParameter)); + sortStatusOutputExtensions(outputComponent); + } + + return outputComponent; + } + + private static TaskOutputComponent updateStatus(TaskOutputComponent outputComponent, String outputParameter, + CodeSystem.DsfPingStatus.Code statusCode) + { + Type valueType = outputComponent.getValue(); + if (valueType instanceof Coding coding) + { + coding.setSystem(CodeSystem.DsfPingStatus.URL).setCode(statusCode.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); + } + else + { + outputComponent.setValue(CodeSystem.DsfPingStatus.fromCode(statusCode)); + } + + List outputTypeCodings = outputComponent.getType().getCoding(); + if (outputTypeCodings.isEmpty()) + { + outputComponent.getType().addCoding().setSystem(CodeSystem.DsfPing.URL).setCode(outputParameter); + } + else + { + if (outputTypeCodings.size() == 1) + { + Coding coding = outputTypeCodings.get(0); + coding.setSystem(CodeSystem.DsfPing.URL).setCode(outputParameter); + } + else + { + outputComponent.getType().setCoding(null); + outputComponent.getType().addCoding().setSystem(CodeSystem.DsfPing.URL).setCode(outputParameter); + } + } + sortStatusOutputExtensions(outputComponent); + + return outputComponent; + } + + private static TaskOutputComponent addTarget(TaskOutputComponent outputComponent, Target target) + { + if (target != null) + { + Extension extension = getOrCreatePingStatusExtension(outputComponent); + + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ORGANIZATION_IDENTIFIER) + .setValue(OrganizationIdentifier.withValue(target.getOrganizationIdentifierValue())); + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ENDPOINT_IDENTIFIER) + .setValue(EndpointIdentifier.withValue(target.getEndpointIdentifierValue())); + extension.addExtension(ConstantsPing.EXTENSION_URL_CORRELATION_KEY, + new StringType(target.getCorrelationKey())); + sortStatusOutputExtensions(outputComponent); + } + + return outputComponent; + } + + private static TaskOutputComponent updateTarget(TaskOutputComponent outputComponent, Target target) + { + Extension extension = getOrCreatePingStatusExtension(outputComponent); + + Extension correlationKeyExtension = extension.getExtensionByUrl(ConstantsPing.EXTENSION_URL_CORRELATION_KEY); + if (correlationKeyExtension != null) + { + correlationKeyExtension.setValue(new StringType(target.getCorrelationKey())); + } + else + { + extension.addExtension(ConstantsPing.EXTENSION_URL_CORRELATION_KEY, + new StringType(target.getCorrelationKey())); + } + + Extension organizationIdentifierExtension = extension + .getExtensionByUrl(ConstantsPing.EXTENSION_URL_ORGANIZATION_IDENTIFIER); + if (organizationIdentifierExtension != null) + { + organizationIdentifierExtension.setValue(new StringType(target.getOrganizationIdentifierValue())); + } + else + { + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ORGANIZATION_IDENTIFIER) + .setValue(OrganizationIdentifier.withValue(target.getOrganizationIdentifierValue())); + } + + Extension urlEndpointIdentifier = extension.getExtensionByUrl(ConstantsPing.EXTENSION_URL_ENDPOINT_IDENTIFIER); + if (urlEndpointIdentifier != null) + { + urlEndpointIdentifier.setValue(new StringType(target.getEndpointIdentifierValue())); + } + else + { + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ENDPOINT_IDENTIFIER) + .setValue(EndpointIdentifier.withValue(target.getEndpointIdentifierValue())); + } + sortStatusOutputExtensions(outputComponent); + + return outputComponent; + } + + private static TaskOutputComponent addErrors(TaskOutputComponent outputComponent, List errors) + { + if (errors != null && !errors.isEmpty()) + { + Extension extension = getOrCreatePingStatusExtension(outputComponent); + Extension errorsExtension = getOrCreateErrorsExtension(extension); + for (ProcessError error : errors) + { + errorsExtension.addExtension(ProcessError.toExtension(error)); + } + } + sortStatusOutputExtensions(outputComponent); + return outputComponent; + } + + private static TaskOutputComponent updateErrors(TaskOutputComponent outputComponent, List errors) + { + Extension extension = getOrCreatePingStatusExtension(outputComponent); + Extension errorsExtension = getOrCreateErrorsExtension(extension); + + if (errors != null) + { + List newErrorExtensions = errors.stream().map(ProcessError::toExtension) + .collect(Collectors.toCollection(ArrayList::new)); + errorsExtension.setExtension(newErrorExtensions); + } + sortStatusOutputExtensions(outputComponent); + + return outputComponent; + } + + private static TaskOutputComponent addNetworkSpeed(TaskOutputComponent outputComponent, BigDecimal downloadSpeed, + String downloadUnit, String downloadUnitSystem, String downloadUnitCode, BigDecimal uploadSpeed, + String uploadUnit, String uploadUnitSystem, String uploadUnitCode) + { + addDownloadSpeed(outputComponent, downloadSpeed, downloadUnit, downloadUnitSystem, downloadUnitCode); + addUploadSpeed(outputComponent, uploadSpeed, uploadUnit, uploadUnitSystem, uploadUnitCode); + + return outputComponent; + } + + private static TaskOutputComponent addDownloadSpeed(TaskOutputComponent outputComponent, BigDecimal downloadSpeed, + String unit, String unitSystem, String unitCode) + { + if (downloadSpeed != null && unit != null && unitSystem != null && unitCode != null) + { + Extension extension = getOrCreatePingStatusExtension(outputComponent); + Extension downloadSpeedExtension = extension.addExtension() + .setUrl(ConstantsPing.EXTENSION_URL_DOWNLOAD_SPEED); + Quantity quantity = new Quantity(); + quantity.setValue(downloadSpeed); + quantity.setUnit(unit); + quantity.setSystem(unitSystem); + quantity.setCode(unitCode); + downloadSpeedExtension.setValue(quantity); + } + sortStatusOutputExtensions(outputComponent); + + return outputComponent; + } + + private static TaskOutputComponent updateDownloadSpeed(TaskOutputComponent outputComponent, + BigDecimal downloadSpeed, String unit, String unitSystem, String unitCode) + { + if (downloadSpeed != null && unit != null && unitSystem != null && unitCode != null) + { + Extension extension = getOrCreatePingStatusExtension(outputComponent); + Extension downloadSpeedExtension = extension.getExtensionByUrl(ConstantsPing.EXTENSION_URL_DOWNLOAD_SPEED); + if (downloadSpeedExtension != null) + { + Quantity quantity = new Quantity(); + quantity.setValue(downloadSpeed); + quantity.setUnit(unit); + quantity.setSystem(unitSystem); + quantity.setCode(unitCode); + downloadSpeedExtension.setValue(quantity); + } + else + { + downloadSpeedExtension = extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_DOWNLOAD_SPEED); + Quantity quantity = new Quantity(); + quantity.setValue(downloadSpeed); + quantity.setUnit(unit); + quantity.setSystem(unitSystem); + quantity.setCode(unitCode); + downloadSpeedExtension.setValue(quantity); + } + } + sortStatusOutputExtensions(outputComponent); + + return outputComponent; + } + + private static TaskOutputComponent addUploadSpeed(TaskOutputComponent outputComponent, BigDecimal uploadSpeed, + String unit, String unitSystem, String unitCode) + { + if (uploadSpeed != null && unit != null && unitSystem != null && unitCode != null) + { + Extension extension = getOrCreatePingStatusExtension(outputComponent); + Extension uploadSpeedExtension = extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_UPLOAD_SPEED); + Quantity quantity = new Quantity(); + quantity.setValue(uploadSpeed); + quantity.setUnit(unit); + quantity.setSystem(unitSystem); + quantity.setCode(unitCode); + uploadSpeedExtension.setValue(quantity); + } + sortStatusOutputExtensions(outputComponent); + + return outputComponent; + } + + private static TaskOutputComponent updateUploadSpeed(TaskOutputComponent outputComponent, BigDecimal uploadSpeed, + String unit, String unitSystem, String unitCode) + { + if (uploadSpeed != null && unit != null && unitSystem != null && unitCode != null) + { + Extension extension = getOrCreatePingStatusExtension(outputComponent); + Extension uploadSpeedExtension = extension.getExtensionByUrl(ConstantsPing.EXTENSION_URL_UPLOAD_SPEED); + if (uploadSpeedExtension != null) + { + Quantity quantity = (Quantity) uploadSpeedExtension.getValue(); + if (quantity != null) + { + quantity.setValue(uploadSpeed); + quantity.setUnit(unit); + quantity.setSystem(unitSystem); + quantity.setCode(unitCode); + } + } + else + { + uploadSpeedExtension = extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_UPLOAD_SPEED); + Quantity quantity = new Quantity(); + quantity.setValue(uploadSpeed); + quantity.setUnit(unit); + quantity.setSystem(unitSystem); + quantity.setCode(unitCode); + uploadSpeedExtension.setValue(quantity); + } + } + sortStatusOutputExtensions(outputComponent); + + return outputComponent; + } + + private static Extension getOrCreatePingStatusExtension(TaskOutputComponent outputComponent) + { + Optional optionalExtension = getPingStatusExtension(outputComponent); + if (optionalExtension.isPresent()) + { + return optionalExtension.get(); + } + else + { + Extension extension = outputComponent.addExtension(); + extension.setUrl(ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_PING_STATUS); + return extension; + } + } + + private static Optional getPingStatusExtension(TaskOutputComponent outputComponent) + { + List pingStatusExtensions = outputComponent.getExtension().stream().filter( + extension -> ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_PING_STATUS.equals(extension.getUrl())) + .toList(); + if (pingStatusExtensions.isEmpty()) + { + return Optional.empty(); + } + else + { + if (pingStatusExtensions.size() == 1) + { + return Optional.of(pingStatusExtensions.get(0)); + } + else + { + throw new RuntimeException( + "Only one ping status extension is allowed but found " + pingStatusExtensions.size()); + } + } + } + + private static Extension getOrCreateErrorsExtension(Extension extension) + { + Optional optionalExtension = getErrorsExtension(extension); + if (optionalExtension.isPresent()) + { + return optionalExtension.get(); + } + else + { + Extension errorsExtension = extension.addExtension(); + errorsExtension.setUrl(ConstantsPing.EXTENSION_URL_ERRORS); + return errorsExtension; + } + } + + private static Optional getErrorsExtension(Extension extension) + { + List errorsExtensions = extension.getExtension().stream() + .filter(ex -> ConstantsPing.EXTENSION_URL_ERRORS.equals(ex.getUrl())).toList(); + if (errorsExtensions.isEmpty()) + { + return Optional.empty(); + } + else + { + if (errorsExtensions.size() == 1) + { + return Optional.of(errorsExtensions.get(0)); + } + else + { + throw new RuntimeException("Only one errors extension is allowed but found " + errorsExtensions.size()); + } + } + } + + private static List getOutputsByExtensionUrlAndCodes(Task task, String... codes) + { + return task.getOutput().stream() + .filter(outputComponent -> outputComponent.getType().getCoding().stream() + .anyMatch(coding -> CodeSystem.DsfPing.URL.equals(coding.getSystem()) + && Stream.of(codes).anyMatch(code -> code.equals(coding.getCode()))) + || outputComponent.getExtension().stream() + .anyMatch(extension -> ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_PING_STATUS + .equals(extension.getUrl()))) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private static void sortStatusOutputExtensions(TaskOutputComponent outputComponent) + { + Optional optPingStatusExtension = getPingStatusExtension(outputComponent); + if (optPingStatusExtension.isPresent()) + { + Extension pingStatusExtension = optPingStatusExtension.get(); + List extensions = pingStatusExtension.getExtension(); + List sortedExtensions = new ArrayList<>(); + + // Extensions representing Target + Optional correlationKeyExtension = extensions.stream() + .filter(extension -> ConstantsPing.EXTENSION_URL_CORRELATION_KEY.equals(extension.getUrl())) + .findFirst(); + Optional organizationIdentifierExtension = extensions.stream() + .filter(extension -> ConstantsPing.EXTENSION_URL_ORGANIZATION_IDENTIFIER.equals(extension.getUrl())) + .findFirst(); + Optional endpointIdentifierExtension = extensions.stream() + .filter(extension -> ConstantsPing.EXTENSION_URL_ENDPOINT_IDENTIFIER.equals(extension.getUrl())) + .findFirst(); + if (organizationIdentifierExtension.isPresent()) + { + extensions.remove(organizationIdentifierExtension.get()); + sortedExtensions.add(organizationIdentifierExtension.get()); + } + if (endpointIdentifierExtension.isPresent()) + { + extensions.remove(endpointIdentifierExtension.get()); + sortedExtensions.add(endpointIdentifierExtension.get()); + } + if (correlationKeyExtension.isPresent()) + { + extensions.remove(correlationKeyExtension.get()); + sortedExtensions.add(correlationKeyExtension.get()); + } + + Optional downloadSpeedExtension = extensions.stream() + .filter(extension -> ConstantsPing.EXTENSION_URL_DOWNLOAD_SPEED.equals(extension.getUrl())) + .findFirst(); + if (downloadSpeedExtension.isPresent()) + { + extensions.remove(downloadSpeedExtension.get()); + sortedExtensions.add(downloadSpeedExtension.get()); + } + + Optional uploadSpeedExtension = extensions.stream() + .filter(extension -> ConstantsPing.EXTENSION_URL_UPLOAD_SPEED.equals(extension.getUrl())) + .findFirst(); + if (uploadSpeedExtension.isPresent()) + { + extensions.remove(uploadSpeedExtension.get()); + sortedExtensions.add(uploadSpeedExtension.get()); + } + + Optional errorsExtension = extensions.stream() + .filter(extension -> ConstantsPing.EXTENSION_URL_ERRORS.equals(extension.getUrl())).findFirst(); + if (errorsExtension.isPresent()) + { + extensions.remove(errorsExtension.get()); + sortedExtensions.add(errorsExtension.get()); + } + + sortedExtensions.addAll(extensions); + pingStatusExtension.setExtension(sortedExtensions); + } + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/GenericPrimitiveTypeSerializer.java b/src/main/java/dev/dsf/bpe/variables/GenericPrimitiveTypeSerializer.java new file mode 100644 index 00000000..9702968b --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/GenericPrimitiveTypeSerializer.java @@ -0,0 +1,100 @@ +package dev.dsf.bpe.variables; + +import java.lang.reflect.InvocationTargetException; +import java.util.Objects; + +import org.camunda.bpm.engine.impl.variable.serializer.PrimitiveValueSerializer; +import org.camunda.bpm.engine.impl.variable.serializer.ValueFields; +import org.camunda.bpm.engine.variable.impl.value.PrimitiveTypeValueImpl; +import org.camunda.bpm.engine.variable.impl.value.UntypedValueImpl; +import org.camunda.bpm.engine.variable.type.PrimitiveValueType; +import org.camunda.bpm.engine.variable.value.PrimitiveValue; +import org.springframework.beans.factory.InitializingBean; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +public abstract class GenericPrimitiveTypeSerializer & PrimitiveValue> + extends PrimitiveValueSerializer implements InitializingBean +{ + private final ObjectMapper objectMapper; + private final Class typeClass; + private final Class valueClass; + private final PrimitiveValueType primitiveValueType; + + public GenericPrimitiveTypeSerializer(PrimitiveValueType variableType, ObjectMapper objectMapper, + Class typeClass, Class valueClass) + { + super(variableType); + this.objectMapper = objectMapper; + this.typeClass = typeClass; + this.valueClass = valueClass; + this.primitiveValueType = variableType; + } + + @Override + public void afterPropertiesSet() + { + Objects.requireNonNull(objectMapper); + } + + @Override + public T readValue(ValueFields valueFields, boolean asTransientValue) + { + try + { + byte[] bytes = valueFields.getByteArrayValue(); + return (bytes == null || bytes.length == 0) ? null + : typeClass.getConstructor(valueClass, PrimitiveValueType.class) + .newInstance(objectMapper.readValue(bytes, valueClass), primitiveValueType); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @Override + public void writeValue(T value, ValueFields valueFields) + { + try + { + valueFields.setByteArrayValue(objectMapper.writeValueAsBytes(value.getValue())); + } + catch (JsonProcessingException e) + { + throw new RuntimeException(e); + } + } + + @Override + public T convertToTypedValue(UntypedValueImpl untypedValue) + { + try + { + if (untypedValue != null && typeClass.isAssignableFrom(untypedValue.getValue().getClass())) + { + T typedValue = typeClass.cast(untypedValue.getValue()); + + return typeClass.getConstructor(typeClass, primitiveValueType.getClass()).newInstance(typedValue, + primitiveValueType); + + } + else if (untypedValue != null) + { + throw new IllegalArgumentException("Cannot convert " + + untypedValue.getValue().getClass().getSimpleName() + " to " + typeClass.getSimpleName()); + } + throw new IllegalArgumentException("Cannot convert " + null + " to " + typeClass.getSimpleName()); + } + catch (NoSuchMethodException e) + { + throw new IllegalArgumentException("Cannot convert " + untypedValue.getValue().getClass().getSimpleName() + + " to " + typeClass.getSimpleName()); + } + catch (InvocationTargetException | IllegalAccessException | InstantiationException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValue.java b/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValue.java new file mode 100644 index 00000000..7614746e --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValue.java @@ -0,0 +1,9 @@ +package dev.dsf.bpe.variables.codesystem.dsfpingstatus; + +import org.camunda.bpm.engine.variable.value.PrimitiveValue; + +import dev.dsf.bpe.CodeSystem; + +public interface CodeValue extends PrimitiveValue +{ +} diff --git a/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValueImpl.java b/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValueImpl.java new file mode 100644 index 00000000..5a0af299 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValueImpl.java @@ -0,0 +1,21 @@ +package dev.dsf.bpe.variables.codesystem.dsfpingstatus; + +import org.camunda.bpm.engine.variable.impl.value.PrimitiveTypeValueImpl; +import org.camunda.bpm.engine.variable.type.PrimitiveValueType; + +import dev.dsf.bpe.CodeSystem; + +public class CodeValueImpl extends PrimitiveTypeValueImpl implements CodeValue +{ + private static final PrimitiveValueType DURATION_VALUE_TYPE = new CodeValueTypeImpl(); + + public CodeValueImpl(CodeSystem.DsfPingStatus.Code value) + { + super(value, DURATION_VALUE_TYPE); + } + + public CodeValueImpl(CodeSystem.DsfPingStatus.Code value, PrimitiveValueType type) + { + super(value, type); + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValueSerializer.java b/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValueSerializer.java new file mode 100644 index 00000000..462f66f1 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValueSerializer.java @@ -0,0 +1,18 @@ +package dev.dsf.bpe.variables.codesystem.dsfpingstatus; + +import org.camunda.bpm.engine.variable.type.PrimitiveValueType; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.variables.GenericPrimitiveTypeSerializer; + +public class CodeValueSerializer extends GenericPrimitiveTypeSerializer +{ + private static final PrimitiveValueType DURATION_VALUE_TYPE = new CodeValueTypeImpl(); + + public CodeValueSerializer() + { + super(DURATION_VALUE_TYPE, new ObjectMapper(), CodeValueImpl.class, CodeSystem.DsfPingStatus.Code.class); + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValueTypeImpl.java b/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValueTypeImpl.java new file mode 100644 index 00000000..95ee1f2c --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/codesystem/dsfpingstatus/CodeValueTypeImpl.java @@ -0,0 +1,31 @@ +package dev.dsf.bpe.variables.codesystem.dsfpingstatus; + +import java.util.Map; + +import org.camunda.bpm.engine.variable.impl.type.PrimitiveValueTypeImpl; + +import dev.dsf.bpe.CodeSystem; + +public class CodeValueTypeImpl extends PrimitiveValueTypeImpl +{ + private static final Class CODE_CLASS = CodeSystem.DsfPingStatus.Code.class; + + public CodeValueTypeImpl() + { + super(CODE_CLASS); + } + + @Override + public CodeValueImpl createValue(Object o, Map map) + { + if (o instanceof CodeSystem.DsfPingStatus.Code code) + { + return new CodeValueImpl(code); + } + else + { + throw new IllegalArgumentException("Cannot create value of type " + CODE_CLASS.getSimpleName() + + " from type " + o.getClass().getSimpleName()); + } + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/duration/DurationValue.java b/src/main/java/dev/dsf/bpe/variables/duration/DurationValue.java new file mode 100644 index 00000000..531ed68d --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/duration/DurationValue.java @@ -0,0 +1,9 @@ +package dev.dsf.bpe.variables.duration; + +import java.time.Duration; + +import org.camunda.bpm.engine.variable.value.PrimitiveValue; + +public interface DurationValue extends PrimitiveValue +{ +} diff --git a/src/main/java/dev/dsf/bpe/variables/duration/DurationValueImpl.java b/src/main/java/dev/dsf/bpe/variables/duration/DurationValueImpl.java new file mode 100644 index 00000000..e5bc5e02 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/duration/DurationValueImpl.java @@ -0,0 +1,21 @@ +package dev.dsf.bpe.variables.duration; + +import java.time.Duration; + +import org.camunda.bpm.engine.variable.impl.value.PrimitiveTypeValueImpl; +import org.camunda.bpm.engine.variable.type.PrimitiveValueType; + +public class DurationValueImpl extends PrimitiveTypeValueImpl implements DurationValue +{ + private static final PrimitiveValueType DURATION_VALUE_TYPE = new DurationValueTypeImpl(); + + public DurationValueImpl(Duration value) + { + super(value, DURATION_VALUE_TYPE); + } + + public DurationValueImpl(Duration value, PrimitiveValueType type) + { + super(value, type); + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/duration/DurationValueSerializer.java b/src/main/java/dev/dsf/bpe/variables/duration/DurationValueSerializer.java new file mode 100644 index 00000000..5e50fb41 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/duration/DurationValueSerializer.java @@ -0,0 +1,19 @@ +package dev.dsf.bpe.variables.duration; + +import java.time.Duration; + +import org.camunda.bpm.engine.variable.type.PrimitiveValueType; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import dev.dsf.bpe.variables.GenericPrimitiveTypeSerializer; + +public class DurationValueSerializer extends GenericPrimitiveTypeSerializer +{ + private static final PrimitiveValueType DURATION_VALUE_TYPE = new DurationValueTypeImpl(); + + public DurationValueSerializer(ObjectMapper objectMapperWithJavaTimeModule) + { + super(DURATION_VALUE_TYPE, objectMapperWithJavaTimeModule, DurationValueImpl.class, Duration.class); + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/duration/DurationValueTypeImpl.java b/src/main/java/dev/dsf/bpe/variables/duration/DurationValueTypeImpl.java new file mode 100644 index 00000000..963329f6 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/duration/DurationValueTypeImpl.java @@ -0,0 +1,30 @@ +package dev.dsf.bpe.variables.duration; + +import java.time.Duration; +import java.util.Map; + +import org.camunda.bpm.engine.variable.impl.type.PrimitiveValueTypeImpl; + +public class DurationValueTypeImpl extends PrimitiveValueTypeImpl +{ + private static final Class DURATION_CLASS = Duration.class; + + public DurationValueTypeImpl() + { + super(DURATION_CLASS); + } + + @Override + public DurationValueImpl createValue(Object o, Map map) + { + if (o instanceof Duration duration) + { + return new DurationValueImpl(duration); + } + else + { + throw new IllegalArgumentException("Cannot create value of type " + DURATION_CLASS.getSimpleName() + + " from type " + o.getClass().getSimpleName()); + } + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValue.java b/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValue.java new file mode 100644 index 00000000..ffe1451e --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValue.java @@ -0,0 +1,9 @@ +package dev.dsf.bpe.variables.process_error; + +import org.camunda.bpm.engine.variable.value.PrimitiveValue; + +import dev.dsf.bpe.ProcessError; + +public interface ProcessErrorValue extends PrimitiveValue +{ +} diff --git a/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValueImpl.java b/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValueImpl.java new file mode 100644 index 00000000..9f8e3f2d --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValueImpl.java @@ -0,0 +1,21 @@ +package dev.dsf.bpe.variables.process_error; + +import org.camunda.bpm.engine.variable.impl.value.PrimitiveTypeValueImpl; +import org.camunda.bpm.engine.variable.type.PrimitiveValueType; + +import dev.dsf.bpe.ProcessError; + +public class ProcessErrorValueImpl extends PrimitiveTypeValueImpl implements ProcessErrorValue +{ + private static final ProcessErrorValueTypeImpl PROCESS_ERROR_VALUE_TYPE = new ProcessErrorValueTypeImpl(); + + public ProcessErrorValueImpl(ProcessError value) + { + super(value, PROCESS_ERROR_VALUE_TYPE); + } + + public ProcessErrorValueImpl(ProcessError value, PrimitiveValueType type) + { + super(value, type); + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValueSerializer.java b/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValueSerializer.java new file mode 100644 index 00000000..079990ba --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValueSerializer.java @@ -0,0 +1,16 @@ +package dev.dsf.bpe.variables.process_error; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.variables.GenericPrimitiveTypeSerializer; + +public class ProcessErrorValueSerializer extends GenericPrimitiveTypeSerializer +{ + private static final ProcessErrorValueTypeImpl PROCESS_ERROR_VALUE_TYPE = new ProcessErrorValueTypeImpl(); + + public ProcessErrorValueSerializer() + { + super(PROCESS_ERROR_VALUE_TYPE, new ObjectMapper(), ProcessErrorValueImpl.class, ProcessError.class); + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValueTypeImpl.java b/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValueTypeImpl.java new file mode 100644 index 00000000..6a5d628b --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/process_error/ProcessErrorValueTypeImpl.java @@ -0,0 +1,32 @@ +package dev.dsf.bpe.variables.process_error; + +import java.util.Map; + +import org.camunda.bpm.engine.variable.impl.type.PrimitiveValueTypeImpl; +import org.camunda.bpm.engine.variable.value.TypedValue; + +import dev.dsf.bpe.ProcessError; + +public class ProcessErrorValueTypeImpl extends PrimitiveValueTypeImpl +{ + private static final Class PROCESS_ERROR_CLASS = ProcessError.class; + + public ProcessErrorValueTypeImpl() + { + super(PROCESS_ERROR_CLASS); + } + + @Override + public TypedValue createValue(Object value, Map valueInfo) + { + if (value instanceof ProcessError error) + { + return new ProcessErrorValueImpl(error); + } + else + { + throw new IllegalArgumentException("Cannot create value of type " + PROCESS_ERROR_CLASS.getSimpleName() + + " from type " + value.getClass().getSimpleName()); + } + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValue.java b/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValue.java new file mode 100644 index 00000000..75c99472 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValue.java @@ -0,0 +1,9 @@ +package dev.dsf.bpe.variables.process_errors; + +import org.camunda.bpm.engine.variable.value.PrimitiveValue; + +import dev.dsf.bpe.ProcessErrors; + +public interface ProcessErrorsValue extends PrimitiveValue +{ +} diff --git a/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValueImpl.java b/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValueImpl.java new file mode 100644 index 00000000..ce646bf2 --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValueImpl.java @@ -0,0 +1,21 @@ +package dev.dsf.bpe.variables.process_errors; + +import org.camunda.bpm.engine.variable.impl.value.PrimitiveTypeValueImpl; +import org.camunda.bpm.engine.variable.type.PrimitiveValueType; + +import dev.dsf.bpe.ProcessErrors; + +public class ProcessErrorsValueImpl extends PrimitiveTypeValueImpl implements ProcessErrorsValue +{ + private static final ProcessErrorsValueTypeImpl PROCESS_ERRORS_VALUE_TYPE = new ProcessErrorsValueTypeImpl(); + + public ProcessErrorsValueImpl(ProcessErrors value) + { + super(value, PROCESS_ERRORS_VALUE_TYPE); + } + + public ProcessErrorsValueImpl(ProcessErrors value, PrimitiveValueType type) + { + super(value, type); + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValueSerializer.java b/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValueSerializer.java new file mode 100644 index 00000000..54dfb15b --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValueSerializer.java @@ -0,0 +1,16 @@ +package dev.dsf.bpe.variables.process_errors; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import dev.dsf.bpe.ProcessErrors; +import dev.dsf.bpe.variables.GenericPrimitiveTypeSerializer; + +public class ProcessErrorsValueSerializer extends GenericPrimitiveTypeSerializer +{ + private static final ProcessErrorsValueTypeImpl TYPE = new ProcessErrorsValueTypeImpl(); + + public ProcessErrorsValueSerializer() + { + super(TYPE, new ObjectMapper(), ProcessErrorsValueImpl.class, ProcessErrors.class); + } +} diff --git a/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValueTypeImpl.java b/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValueTypeImpl.java new file mode 100644 index 00000000..d26b497d --- /dev/null +++ b/src/main/java/dev/dsf/bpe/variables/process_errors/ProcessErrorsValueTypeImpl.java @@ -0,0 +1,32 @@ +package dev.dsf.bpe.variables.process_errors; + +import java.util.Map; + +import org.camunda.bpm.engine.variable.impl.type.PrimitiveValueTypeImpl; +import org.camunda.bpm.engine.variable.value.TypedValue; + +import dev.dsf.bpe.ProcessErrors; + +public class ProcessErrorsValueTypeImpl extends PrimitiveValueTypeImpl +{ + private static final Class PROCESS_ERRORS_CLASS = ProcessErrors.class; + + public ProcessErrorsValueTypeImpl() + { + super(PROCESS_ERRORS_CLASS); + } + + @Override + public TypedValue createValue(Object value, Map valueInfo) + { + if (value instanceof ProcessErrors errors) + { + return new ProcessErrorsValueImpl(errors); + } + else + { + throw new IllegalArgumentException("Cannot create value of type " + PROCESS_ERRORS_CLASS.getSimpleName() + + " from type " + value.getClass().getSimpleName()); + } + } +} diff --git a/src/main/resources/bpe/ping-autostart.bpmn b/src/main/resources/bpe/ping-autostart.bpmn index c77a2c4d..c23a2b2f 100644 --- a/src/main/resources/bpe/ping-autostart.bpmn +++ b/src/main/resources/bpe/ping-autostart.bpmn @@ -100,7 +100,7 @@ - + no diff --git a/src/main/resources/bpe/ping.bpmn b/src/main/resources/bpe/ping.bpmn index 5d1ba29e..3d46927d 100644 --- a/src/main/resources/bpe/ping.bpmn +++ b/src/main/resources/bpe/ping.bpmn @@ -1,265 +1,599 @@ - - - - - - SequenceFlow_0k1j79c - Flow_0j92st0 - + + + + + - Flow_0j92st0 - Flow_099pk09 + Flow_1bfs68o R3/PT5S - Flow_0v2ascf + Flow_10epxa2 - - - - - + Flow_0y9usku Flow_1fjeq2h - - PT20S + + ${execution.hasVariable("pongTimerDuration") ? pongTimerDuration : "PT30S"} - - + + + R3/PT5S + Flow_1lghrxh - Flow_03hkxbe - + Flow_1ewmc79 + Flow_1j54c2s - Flow_1lghrxh Flow_0y9usku + Flow_1lghrxh - - + Flow_1fjeq2h Flow_136htek - - - Flow_03hkxbe - Flow_0wmpprs - - Flow_1ipvu5v + Flow_08vgmf6 Flow_1j54c2s Flow_101sqed - - - Flow_101sqed - Flow_16ssf4a - - - ${execution.hasVariable('statusCode') && (statusCode == 'not-allowed' || statusCode == 'not-reachable')} - - - - Flow_0v2ascf - Flow_1ipvu5v - - - - http://dsf.dev/bpe/Process/pong|#{version} - - - http://dsf.dev/fhir/StructureDefinition/task-ping|#{version} - - - ping - - - - - - - - Flow_0wmpprs Flow_136htek - Flow_16ssf4a + Flow_0upu487 Flow_1ho1hys - - - R3/PT5S Flow_1ho1hys + + + + http://dsf.dev/bpe/Process/pong|#{version} + + + ping + + + http://dsf.dev/fhir/StructureDefinition/task-ping|#{version} + + + Flow_10epxa2 + Flow_08vgmf6 + + + Flow_1jwekqw + Flow_13u1nzy + Flow_1yt9547 + + + Flow_1yt9547 + Flow_1ncztgo + + + + + http://dsf.dev/fhir/StructureDefinition/task-cleanup-pong|#{version} + + + cleanupPong + + + http://dsf.dev/bpe/Process/pong|#{version} + + + Flow_0jcfur3 + Flow_18wxsor + Flow_0klalf8 + + + Flow_0qif01p + Flow_0jcfur3 + + + Flow_13u1nzy + Flow_0klalf8 + Flow_03nx6rk + + + + R3/PT5S + + Flow_03nx6rk + + + + + + + + + + ${execution.hasVariable('statusCode') && (statusCode.getValue() == 'not-allowed' || statusCode.getValue() == 'not-reachable')} + + + ${downloadResourceSizeBytes < 0} + + + + + + + Flow_1ewmc79 + Flow_1jwekqw + + + + + + Flow_101sqed + Flow_0upu487 + + + Flow_1ncztgo + Flow_0qif01p + Flow_18wxsor + + + + ${execution.hasVariable('resourceDownloadError')} + + + + + + ping + + + Flow_152nb9r + Flow_1op4sei + + + + + ping + + + Flow_1bfs68o + Flow_0gubpgz + + + Flow_0gubpgz + Flow_1du5wys + Flow_1du5wys + + Flow_0iuuyo1 + Flow_0ejw9k5 + Flow_1qyomuj + Flow_1c15ef2 + + + Flow_1c15ef2 + Flow_0j92st0 + + + Flow_0fxlsv3 + Flow_152nb9r + Flow_0iuuyo1 + - SequenceFlow_0k1j79c - + Flow_1hor4v7 + - + + + - - - Flow_099pk09 - Flow_1du5wys + + ${downloadResourceSizeBytes < 0} + + + + + Flow_0xy3953 + Flow_0fxlsv3 + + + Flow_0z3dgxw + Flow_0ejw9k5 + + + Flow_1op4sei + Flow_0z3dgxw + Flow_1qyomuj + + + + ${execution.hasVariable('resourceUploadError')} + + + + + + Flow_1hor4v7 + Flow_0xy3953 + + + Includes download speeds and errors of all pongs + + + generates up to maxUploadSizeBystes + + + Process Parameters +- downloadResourceSizeBytes read from Input Paramater (default tbd) +- maxDownloadSizeBytes read from environment variable (default tbd) +- maxUpdloadSizeBytes read from environment variables (default tbd) + + + Cleanup steps: +- delete generated resource + + + + + + Log = logger +Save = save as execution variable +Store = store on DSF FHIR server either as separate resource or output parameter + + + Information in message: +- dowloadResourceSize: long +- downloadResourceReference: Reference + + + + Information in message: +- downloadDurationMillis: long +- downloadedBytes: long + + + + downloads up to maxDownloadSizeBytes + + - - + - - + + + + + + + + - - + + - - + + - - + + - + - - + + - + - - + + - - + + - - + + + + + + + + + + + - - + + - - + + - - - - - + + + + + + + + + + + + + + + + + - - + + - - + + - - - - + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - + + + + + + + + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + - - - + + + - - - - + + + + - - - - + + + - - - + + + - - + + + + + + + + + + + - - - - - + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - + + + diff --git a/src/main/resources/bpe/pong.bpmn b/src/main/resources/bpe/pong.bpmn index 39fdb0be..6116cda6 100644 --- a/src/main/resources/bpe/pong.bpmn +++ b/src/main/resources/bpe/pong.bpmn @@ -1,97 +1,541 @@ - - + + - SequenceFlow_07w11cw - + Flow_19b3cp4 + - - - - SequenceFlow_1ism9wt - - - - http://dsf.dev/fhir/StructureDefinition/task-pong|#{version} - - - pong - - - http://dsf.dev/bpe/Process/ping|#{version} - - - + + Flow_1gap0hi + Flow_10z0d4x + Flow_08gidyv + + + Flow_10z0d4x + Flow_0vqsjzn + + + Flow_08gidyv + Flow_0gvrnxd + Flow_0dp8f59 + Flow_1o3n9u6 + + + + + http://dsf.dev/bpe/Process/ping|#{version} + + + pong + + + http://dsf.dev/fhir/StructureDefinition/task-pong|#{version} + + + Flow_1o3n9u6 + Flow_0fzmjzb + + + Flow_0fzmjzb + Flow_0h8flp6 + Flow_1bzjspe + Flow_00t1ck1 + + + Flow_1bzjspe + Flow_0rj915n + Flow_17x98wg + + + Flow_0rj915n + Flow_1ttsk1o + + + + Flow_17x98wg + Flow_1xfk4ds + + ${execution.hasVariable("cleanupTimerDuration") ? cleanupTimerDuration : "PT20S"} + + + + Flow_1jehvly + Flow_0yujsot + Flow_0x7t1ii + + + Flow_1w3kcjh + Flow_1jehvly + + + + + pong + + + Flow_00t1ck1 + Flow_1lfcycx + Flow_0h8flp6 + Flow_0zib7wr + + + + + pong + + + Flow_0x7t1ii + Flow_033lqly + + + Flow_17wqhp8 + Flow_0gvrnxd + + + + ${downloadResourceSizeBytes < 0} + + + + + + ${execution.hasVariable('statusCode') && (statusCode.getValue() == 'not-reachable' || statusCode.getValue() == 'not-allowed')} + + + + ${downloadResourceSizeBytes < 0} + + + + + + Flow_1j5lf0u + Flow_1oo1n55 + + + Flow_1oo1n55 + Flow_1fzloso + + + + Flow_1bgedez - - - SequenceFlow_07w11cw - SequenceFlow_09i9zb8 + + + + Flow_1eh8lho + Flow_1n4fb8d + Flow_1lfcycx + + + + + Flow_1ttsk1o + Flow_1eh8lho + + + + + Flow_0pp3r2w + Flow_0yujsot + + + + + Flow_1xfk4ds + Flow_1n4fb8d - - SequenceFlow_09i9zb8 - SequenceFlow_1ism9wt + + + Flow_0zib7wr + Flow_1bgedez - - - Flow_0yr2pmf + + Flow_19b3cp4 + Flow_1j5lf0u + + + + Flow_1fzloso + Flow_1gap0hi + + + Flow_0qxt5zo - - Flow_0yr2pmf - + + Flow_0qxt5zo + - + + + + + Flow_0vqsjzn + Flow_0pp3r2w + Flow_1w3kcjh + + + + + ${execution.hasVariable('resourceDownloadError')} + + + + + Flow_033lqly + Flow_17wqhp8 + Flow_15xatzp + + + + ${execution.hasVariable('resourceUploadError')} + + + Flow_15xatzp + Flow_0dp8f59 + + + + Log = logger +Save = save as execution variable +Store = store on DSF FHIR server either as separate resource or output parameter + + + downloads up to maxDownloadSizeBytes + + + Information in message: +- downloadedBytes: long +- downloadedDuration: Duration +- downloadResourceReference: Reference +- errors: List + + + Cleanup steps: +- delete generated resource + + + Estimation based on download duration e.g. 10x or 20x download duration + + + generates up to maxUploadSizeBystes + + + Process Parameters +- downloadResourceSizeBytes read from Input Paramater (no default, value from message expected) +- maxDownloadSizeBytes read from environment variable (default tbd) +- maxUpdloadSizeBytes read from environment variables (default tbd) + + + + + + - + + + - + - + - - + + + + + + + + + + + + + + + + + + + + + + - + - - + + + + + + + + - - + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + - - - - + + + diff --git a/src/main/resources/fhir/ActivityDefinition/dsf-pong.xml b/src/main/resources/fhir/ActivityDefinition/dsf-pong.xml index 1a877ce9..efde8372 100644 --- a/src/main/resources/fhir/ActivityDefinition/dsf-pong.xml +++ b/src/main/resources/fhir/ActivityDefinition/dsf-pong.xml @@ -31,6 +31,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/fhir/CodeSystem/dsf-ping-error.xml b/src/main/resources/fhir/CodeSystem/dsf-ping-error.xml new file mode 100644 index 00000000..5724256a --- /dev/null +++ b/src/main/resources/fhir/CodeSystem/dsf-ping-error.xml @@ -0,0 +1,384 @@ + + + + + + + + + + + + + <!-- status managed by bpe --> + <status value="unknown" /> + <experimental value="false" /> + <!-- date managed by bpe --> + <date value="#{date}" /> + <publisher value="DSF" /> + <description value="CodeSystem error codes that might occur during execution." /> + <caseSensitive value="true" /> + <hierarchyMeaning value="grouped-by" /> + <versionNeeded value="false" /> + <content value="complete" /> + <concept> + <code value="send-message-http-401"/> + <display value="Sending a message to the remote instance resulted in HTTP status 401"/> + </concept> + <concept> + <code value="send-message-http-403"/> + <display value="Sending a message to the remote instance resulted in HTTP status 403"/> + </concept> + <concept> + <code value="send-reference-message-http-403"/> + <display value="Sending a message including a reference to the remote instance resulted in HTTP status 403"/> + </concept> + <concept> + <code value="send-message-http-500"/> + <display value="Sending a message to the remote instance resulted in HTTP status 500"/> + </concept> + <concept> + <code value="send-message-http-502"/> + <display value="Sending a message to the remote instance resulted in HTTP status 502"/> + </concept> + <concept> + <code value="send-message-http-unexpected"/> + <display value="Sending a message to the remote instance resulted in an unexpected HTTP status code"/> + </concept> + <concept> + <code value="send-message-ssl-handshake"/> + <display value="Sending a message to the remote instance was unsuccessful because of a failed SSL handshake"/> + </concept> + <concept> + <code value="send-message-connect-timeout"/> + <display value="Sending a message to the remote instance was unsuccessful because of a connection timeout"/> + </concept> + <concept> + <code value="send-message-http-host-connect"/> + <display value="Sending a message to the remote instance was unsuccessful because the connection was refused"/> + </concept> + <concept> + <code value="send-message-unknown-host"/> + <display value="Sending a message to the remote instance was unsuccessful because the target hostname could not be resolved"/> + </concept> + + + <concept> + <code value="receive-message-http-401"/> + <display value="Received a message and responded with HTTP status 401"/> + </concept> + <concept> + <code value="receive-message-http-403"/> + <display value="Received a message and responded with HTTP status 403"/> + </concept> + <concept> + <code value="receive-reference-message-http-403"/> + <display value="Received a message including a reference and responded with HTTP status 403"/> + </concept> + <concept> + <code value="receive-message-http-500"/> + <display value="Received a message and responded with HTTP status 500"/> + </concept> + <concept> + <code value="receive-message-http-502"/> + <display value="Received a message and responded with HTTP status 502"/> + </concept> + <concept> + <code value="receive-message-http-unexpected"/> + <display value="Received a message and responded with an unexpected HTTP status code"/> + </concept> + <concept> + <code value="receive-message-ssl-handshake"/> + <display value="Receiving a message was unsuccessful because of a failed SSL handshake"/> + </concept> + <concept> + <code value="receive-message-connect-timeout"/> + <display value="Receiving a message was unsuccessful because of a connection timeout"/> + </concept> + <concept> + <code value="receive-message-http-host-connect"/> + <display value="Receiving a message was unsuccessful because the connection was refused"/> + </concept> + <concept> + <code value="receive-message-unknown-host"/> + <display value="Receiving a message was unsuccessful because the target hostname could not be resolved"/> + </concept> + + <concept> + <code value="local-binary-delete-timeout-connect"/> + <display value="Local instance encountered a connect timeout trying to clean up the binary resource"/> + </concept> + <concept> + <code value="local-binary-delete-timeout-read"/> + <display value="Local instance encountered a read timeout trying to clean up the binary resource"/> + </concept> + <concept> + <code value="local-binary-delete-http-host-connect"/> + <display value="Local instance was unable to clean up the binary resource from the local DSF FHIR server because the connection was refused"/> + </concept> + <concept> + <code value="local-binary-delete-http-401"/> + <display value="Local instance encountered a HTTP status 401 trying to clean up the binary resource"/> + </concept> + <concept> + <code value="local-binary-delete-http-403"/> + <display value="Local instance encountered a HTTP status 403 trying to clean up the binary resource"/> + </concept> + <concept> + <code value="local-binary-delete-http-500"/> + <display value="Local instance encountered a HTTP status 500 trying to clean up the binary resource"/> + </concept> + <concept> + <code value="local-binary-delete-http-502"/> + <display value="Local instance encountered a HTTP status 502 trying to clean up the binary resource"/> + </concept> + <concept> + <code value="local-binary-delete-http-unexpected"/> + <display value="Local instance encountered an unexpected HTTP status code trying to clean up the binary resource"/> + </concept> + + <concept> + <code value="remote-binary-delete-timeout-connect"/> + <display value="Remote instance encountered a connect timeout trying to clean up the binary resource"/> + </concept> + <concept> + <code value="remote-binary-delete-timeout-read"/> + <display value="Remote instance encountered a read timeout trying to clean up the binary resource"/> + </concept> + <concept> + <code value="remote-binary-delete-http-host-connect"/> + <display value="Remote instance was unable to clean up the binary resource from its DSF FHIR server because the connection was refused"/> + </concept> + <concept> + <code value="remote-binary-delete-http-401"/> + <display value="Remote instance encountered a HTTP status 401 trying to clean up the binary resource"/> + </concept> + <concept> + <code value="remote-binary-delete-http-403"/> + <display value="Remote instance encountered a HTTP status 403 trying to clean up the binary resource"/> + </concept> + <concept> + <code value="remote-binary-delete-http-500"/> + <display value="Remote instance encountered a HTTP status 500 trying to clean up the binary resource"/> + </concept> + <concept> + <code value="remote-binary-delete-http-502"/> + <display value="Remote instance encountered a HTTP status 502 trying to clean up the binary resource"/> + </concept> + <concept> + <code value="remote-binary-delete-http-unexpected"/> + <display value="Remote instance encountered an unexpected HTTP status code trying to clean up the binary resource"/> + </concept> + + <concept> + <code value="local-binary-post-http-401"/> + <display value="Local instance encountered a HTTP status 401 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="local-binary-post-http-403"/> + <display value="Local instance encountered a HTTP status 403 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="local-binary-post-http-413"/> + <display value="Local instance encountered a HTTP status 413 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="local-binary-post-http-500"/> + <display value="Local instance encountered a HTTP status 500 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="local-binary-post-http-502"/> + <display value="Local instance encountered a HTTP status 502 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="local-binary-post-http-unexpected"/> + <display value="Local instance encountered an unexpected HTTP status code trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="local-binary-post-timeout-connect"/> + <display value="Local instance encountered a connect timeout trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="local-binary-post-timeout-read"/> + <display value="Local instance encountered a read timeout trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="local-binary-post-http-host-connect"/> + <display value="Local instance was unable to post the binary resource to its own FHIR server because the connection was refused"/> + </concept> + + <concept> + <code value="remote-binary-post-http-401"/> + <display value="Remote instance encountered a HTTP status 401 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="remote-binary-post-http-403"/> + <display value="Remote instance encountered a HTTP status 403 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="remote-binary-post-http-413"/> + <display value="Remote instance encountered a HTTP status 413 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="remote-binary-post-http-500"/> + <display value="Remote instance encountered a HTTP status 500 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="remote-binary-post-http-502"/> + <display value="Remote instance encountered a HTTP status 502 trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="remote-binary-post-http-unexpected"/> + <display value="Remote instance encountered an unexpected HTTP status code trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="remote-binary-post-timeout-connect"/> + <display value="Remote instance encountered a connect timeout trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="remote-binary-post-timeout-read"/> + <display value="Remote instance encountered a read timeout trying to post the binary resource to its own FHIR server"/> + </concept> + <concept> + <code value="remote-binary-post-http-host-connect"/> + <display value="Remote instance was unable to post the binary resource to its own DSF FHIR server because the connection was refused"/> + </concept> + + <concept> + <code value="response-message-timeout-status-requested"/> + <display value="Response missing, sent Task status is 'requested'"/> + </concept> + <concept> + <code value="response-message-timeout-status-in-progress"/> + <display value="Response missing, sent Task status is 'in-progress'"/> + </concept> + <concept> + <code value="response-message-timeout-status-failed"/> + <display value="Response missing, sent Task status is 'failed'"/> + </concept> + <concept> + <code value="response-message-timeout-status-completed"/> + <display value="Response missing, sent Task status is 'completed'"/> + </concept> + <concept> + <code value="response-message-timeout-status-unexpected"/> + <display value="Response missing, sent Task status is neither of 'requested', 'in-progress', 'failed' or 'completed'"/> + </concept> + + <concept> + <code value="response-message-timeout-http-401"/> + <display value="Response message timed out. Received HTTP status 401 trying to check request status on the target"/> + </concept> + <concept> + <code value="response-message-timeout-http-403"/> + <display value="Response message timed out. Received HTTP status 403 trying to check request status on the target"/> + </concept> + <concept> + <code value="response-message-timeout-http-500"/> + <display value="Response message timed out. Received HTTP status 500 trying to check request status on the target"/> + </concept> + <concept> + <code value="response-message-timeout-http-502"/> + <display value="Response message timed out. Received HTTP status 502 trying to check request status on the target"/> + </concept> + <concept> + <code value="response-message-timeout-http-unexpected"/> + <display value="Response message timed out. Received an unexpected HTTP status code trying to check request status on the target"/> + </concept> + + <concept> + <code value="cleanup-message-timeout"/> + <display value="Timeout while waiting for cleanup message from remote instance"/> + </concept> + + <concept> + <code value="local-binary-download-io-error"/> + <display value="Local instance encountered an I/O error trying to download the binary resource from the target"/> + </concept> + <concept> + <code value="local-binary-download-http-401"/> + <display value="Local instance received HTTP status 401 trying to download the binary resource from the target"/> + </concept> + <concept> + <code value="local-binary-download-http-403"/> + <display value="Local instance received HTTP status 403 trying to download the binary resource from the target"/> + </concept> + <concept> + <code value="local-binary-download-http-500"/> + <display value="Local instance received HTTP status 500 trying to download the binary resource from the target"/> + </concept> + <concept> + <code value="local-binary-download-http-502"/> + <display value="Local instance received HTTP status 500 trying to download the binary resource from the target"/> + </concept> + <concept> + <code value="local-binary-download-http-unexpected"/> + <display value="Local instance received an unexpected HTTP status trying to download the binary resource from the target"/> + </concept> + <concept> + <code value="local-binary-download-timeout-connect"/> + <display value="Local instance encountered a connect timeout trying to download the binary resource from the target"/> + </concept> + <concept> + <code value="local-binary-download-timeout-read"/> + <display value="Local instance encountered a read timeout trying to download the binary resource from the target"/> + </concept> + <concept> + <code value="local-binary-download-http-host-connect"/> + <display value="Local instance was unable to download the binary resource from the remote DSF FHIR server because the connection was refused"/> + </concept> + <concept> + <code value="local-binary-download-missing-reference"/> + <display value="Local instance was unable to download the binary resource from the target because the reference was missing"/> + </concept> + + <concept> + <code value="remote-binary-download-io-error"/> + <display value="Remote instance encountered an I/O error trying to download the binary resource from this server"/> + </concept> + <concept> + <code value="remote-binary-download-http-401"/> + <display value="Remote instance received HTTP status 401 trying to download the binary resource from this server"/> + </concept> + <concept> + <code value="remote-binary-download-http-403"/> + <display value="Remote instance received HTTP status 403 trying to download the binary resource from this server"/> + </concept> + <concept> + <code value="remote-binary-download-http-500"/> + <display value="Remote instance received HTTP status 500 trying to download the binary resource from this server"/> + </concept> + <concept> + <code value="remote-binary-download-http-502"/> + <display value="Remote instance received HTTP status 502 trying to download the binary resource from this server"/> + </concept> + <concept> + <code value="remote-binary-download-http-unexpected"/> + <display value="Remote instance received an unexpected HTTP status trying to download the binary resource from this server"/> + </concept> + <concept> + <code value="remote-binary-download-timeout-connect"/> + <display value="Remote instance encountered a connect timeout trying to download the binary resource from this server"/> + </concept> + <concept> + <code value="remote-binary-download-timeout-read"/> + <display value="Remote instance encountered a read timeout trying to download the binary resource from this server"/> + </concept> + <concept> + <code value="remote-binary-download-http-host-connect"/> + <display value="Remote instance was unable to download the binary resource from the local DSF FHIR server because the connection was refused"/> + </concept> + <concept> + <code value="remote-binary-download-missing-reference"/> + <display value="Remote instance was unable to download the binary resource from this server because the reference was missing"/> + </concept> + + <concept> + <code value="local-unknown"/> + <display value="An unknown error was encountered by the local instance"/> + </concept> + <concept> + <code value="remote-unknown"/> + <display value="An unknown error was encountered by the remote instance"/> + </concept> +</CodeSystem> \ No newline at end of file diff --git a/src/main/resources/fhir/CodeSystem/dsf-ping-status.xml b/src/main/resources/fhir/CodeSystem/dsf-ping-status.xml index 1045752c..8dfaf7e5 100644 --- a/src/main/resources/fhir/CodeSystem/dsf-ping-status.xml +++ b/src/main/resources/fhir/CodeSystem/dsf-ping-status.xml @@ -43,7 +43,7 @@ </concept> <concept> <code value="pong-send" /> - <display value="Pong send" /> + <display value="Pong sent" /> <definition value="Pong successfully sent to target organization" /> </concept> </CodeSystem> \ No newline at end of file diff --git a/src/main/resources/fhir/CodeSystem/dsf-ping.xml b/src/main/resources/fhir/CodeSystem/dsf-ping.xml index a79fc556..100877d6 100644 --- a/src/main/resources/fhir/CodeSystem/dsf-ping.xml +++ b/src/main/resources/fhir/CodeSystem/dsf-ping.xml @@ -46,4 +46,39 @@ <display value="Timer Interval" /> <definition value="Interval between two autostarts of the ping process" /> </concept> + <concept> + <code value="download-resource-size-bytes"/> + <display value="Download Resource Size Bytes"/> + <definition value="Size of the resource to download for speed measurements in bytes"/> + </concept> + <concept> + <code value="download-resource-reference" /> + <display value="Download Resource Reference"/> + <definition value="Reference to the resource to be downloaded for measuring network speed" /> + </concept> + <concept> + <code value="network-speed"/> + <display value="Network Speed"/> + <definition value="Network speed in both upload and download of a specific endpoint"/> + </concept> + <concept> + <code value="downloaded-bytes"/> + <display value="Downloaded Bytes"/> + <definition value="Amount of bytes downloaded to measure network speed"/> + </concept> + <concept> + <code value="downloaded-duration"/> + <display value="Downloaded Duration"/> + <definition value="Duration it took for the resource to be downloaded to measure network speed"/> + </concept> + <concept> + <code value="error"/> + <display value="Error"/> + <definition value="Error to be added as an output parameter in the Start Ping Task"/> + </concept> + <concept> + <code value="pong-timeout-duration"/> + <display value="Pong Timeout Duration"/> + <definition value="Time to wait until a pong message is received in ISO 8601 duration format e.g. PT1H5M30S for a duration of 1 hour, 5 minutes and 30 seconds"/> + </concept> </CodeSystem> \ No newline at end of file diff --git a/src/main/resources/fhir/StructureDefinition/dsf-extension-error.xml b/src/main/resources/fhir/StructureDefinition/dsf-extension-error.xml new file mode 100644 index 00000000..6476c0fa --- /dev/null +++ b/src/main/resources/fhir/StructureDefinition/dsf-extension-error.xml @@ -0,0 +1,101 @@ +<StructureDefinition xmlns="http://hl7.org/fhir"> + <meta> + <tag> + <system value="http://dsf.dev/fhir/CodeSystem/read-access-tag"/> + <code value="ALL"/> + </tag> + </meta> + <url value="http://dsf.dev/fhir/StructureDefinition/extension-error"/> + <!-- version managed by bpe --> + <version value="#{version}" /> + <name value="Error"/> + <!-- status managed by bpe --> + <status value="unknown" /> + <experimental value="false" /> + <!-- date managed by bpe --> + <date value="#{date}" /> + <fhirVersion value="4.0.1"/> + <kind value="complex-type"/> + <abstract value="false"/> + <context> + <type value="element"/> + <expression value="Extension"/> + </context> + <context> + <type value="element"/> + <expression value="Task.input"/> + </context> + <context> + <type value="element"/> + <expression value="Task.output"/> + </context> + <type value="Extension"/> + <baseDefinition value="http://hl7.org/fhir/StructureDefinition/Extension"/> + <derivation value="constraint"/> + <differential> + <element id="Extension.extension"> + <path value="Extension.extension"/> + <slicing> + <discriminator> + <type value="value"/> + <path value="url"/> + </discriminator> + <rules value="open"/> + </slicing> + <min value="1"/> + <max value="2"/> + </element> + <element id="Extension.extension:error"> + <path value="Extension.extension"/> + <sliceName value="error"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Extension.extension:error.url"> + <path value="Extension.extension.url"/> + <fixedUri value="error"/> + </element> + <element id="Extension.extension:error.value[x]"> + <path value="Extension.extension.value[x]"/> + <min value="1"/> + <max value="1"/> + <type> + <code value="Coding"/> + </type> + </element> + <element id="Extension.extension:error.value[x].system"> + <path value="Extension.extension.value[x].system"/> + <min value="1"/> + </element> + <element id="Extension.extension:error.value[x].code"> + <path value="Extension.extension.value[x].code"/> + <min value="1"/> + </element> + <element id="Extension.extension:potential-fix"> + <path value="Extension.extension"/> + <sliceName value="potential-fix"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Extension.extension:potential-fix.url"> + <path value="Extension.extension.url"/> + <fixedUri value="potential-fix"/> + </element> + <element id="Extension.extension:potential-fix.value[x]"> + <path value="Extension.extension.value[x]"/> + <min value="1"/> + <max value="1"/> + <type> + <code value="url"/> + </type> + </element> + <element id="Extension.url"> + <path value="Extension.url"/> + <fixedUri value="http://dsf.dev/fhir/StructureDefinition/extension-error"/> + </element> + <element id="Extension.value[x]"> + <path value="Extension.value[x]"/> + <max value="0"/> + </element> + </differential> +</StructureDefinition> \ No newline at end of file diff --git a/src/main/resources/fhir/StructureDefinition/dsf-extension-ping-status.xml b/src/main/resources/fhir/StructureDefinition/dsf-extension-ping-status.xml index 83251d5a..5d42cc76 100644 --- a/src/main/resources/fhir/StructureDefinition/dsf-extension-ping-status.xml +++ b/src/main/resources/fhir/StructureDefinition/dsf-extension-ping-status.xml @@ -122,6 +122,113 @@ <code value="string"/> </type> </element> + <element id="Extension.extension:download-speed-from-remote"> + <path value="Extension.extension"/> + <sliceName value="download-speed-from-remote"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Extension.extension:download-speed-from-remote.url"> + <path value="Extension.extension.url"/> + <fixedUri value="download-speed-from-remote"/> + </element> + <element id="Extension.extension:download-speed-from-remote.value[x]"> + <path value="Extension.extension.value[x]"/> + <min value="1"/> + <max value="1"/> + <type> + <code value="Quantity"/> + </type> + </element> + <element id="Extension.extension:download-speed-from-remote.value[x].value"> + <path value="Extension.extension.value[x].value"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Extension.extension:download-speed-from-remote.value[x].unit"> + <path value="Extension.extension.value[x].unit"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Extension.extension:download-speed-from-remote.value[x].system"> + <path value="Extension.extension.value[x].system"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Extension.extension:download-speed-from-remote.value[x].code"> + <path value="Extension.extension.value[x].code"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Extension.extension:upload-speed-to-remote"> + <path value="Extension.extension"/> + <sliceName value="upload-speed-to-remote"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Extension.extension:upload-speed-to-remote.url"> + <path value="Extension.extension.url"/> + <fixedUri value="upload-speed-to-remote"/> + </element> + <element id="Extension.extension:upload-speed-to-remote.value[x]"> + <path value="Extension.extension.value[x]"/> + <min value="1"/> + <max value="1"/> + <type> + <code value="Quantity"/> + </type> + </element> + <element id="Extension.extension:upload-speed-to-remote.value[x].value"> + <path value="Extension.extension.value[x].value"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Extension.extension:upload-speed-to-remote.value[x].unit"> + <path value="Extension.extension.value[x].unit"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Extension.extension:upload-speed-to-remote.value[x].system"> + <path value="Extension.extension.value[x].system"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Extension.extension:upload-speed-to-remote.value[x].code"> + <path value="Extension.extension.value[x].code"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Extension.extension:errors"> + <path value="Extension.extension"/> + <sliceName value="error"/> + <min value="0"/> + </element> + <element id="Extension.extension:errors.extension"> + <path value="Extension.extension.extension" /> + <slicing> + <discriminator> + <type value="value" /> + <path value="url" /> + </discriminator> + <rules value="open" /> + </slicing> + </element> + <element id="Extension.extension:errors.extension:error-extension"> + <path value="Extension.extension.extension" /> + <sliceName value="error-extension" /> + <type> + <code value="Extension" /> + <profile value="http://dsf.dev/fhir/StructureDefinition/extension-error|#{version}" /> + </type> + </element> + <element id="Extension.extension:errors.url"> + <path value="Extension.extension.url"/> + <fixedUri value="errors"/> + </element> + <element id="Extension.extension:errors.value[x]"> + <path value="Extension.extension.value[x]"/> + <max value="0"/> + </element> <element id="Extension.url"> <path value="Extension.url"/> <fixedUri value="http://dsf.dev/fhir/StructureDefinition/extension-ping-status"/> diff --git a/src/main/resources/fhir/StructureDefinition/dsf-task-cleanup-pong.xml b/src/main/resources/fhir/StructureDefinition/dsf-task-cleanup-pong.xml new file mode 100644 index 00000000..21d165e0 --- /dev/null +++ b/src/main/resources/fhir/StructureDefinition/dsf-task-cleanup-pong.xml @@ -0,0 +1,156 @@ +<StructureDefinition xmlns="http://hl7.org/fhir"> + <meta> + <tag> + <system value="http://dsf.dev/fhir/CodeSystem/read-access-tag" /> + <code value="ALL" /> + </tag> + </meta> + <url value="http://dsf.dev/fhir/StructureDefinition/task-cleanup-pong" /> + <!-- version managed by bpe --> + <version value="#{version}" /> + <name value="TaskCleanupPong" /> + <!-- status managed by bpe --> + <status value="unknown" /> + <experimental value="false" /> + <!-- date managed by bpe --> + <date value="#{date}" /> + <fhirVersion value="4.0.1" /> + <kind value="resource" /> + <abstract value="false" /> + <type value="Task" /> + <baseDefinition value="http://dsf.dev/fhir/StructureDefinition/task-base" /> + <derivation value="constraint" /> + <differential> + <element id="Task.instantiatesCanonical"> + <path value="Task.instantiatesCanonical" /> + <fixedCanonical value="http://dsf.dev/bpe/Process/pong|#{version}" /> + </element> + <element id="Task.input"> + <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> + <valueString value="Parameter" /> + </extension> + <path value="Task.input" /> + <min value="2" /> + <max value="4" /> + </element> + <element id="Task.input:message-name"> + <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> + <valueString value="Parameter" /> + </extension> + <path value="Task.input" /> + <sliceName value="message-name" /> + </element> + <element id="Task.input:message-name.value[x]"> + <path value="Task.input.value[x]" /> + <fixedString value="cleanupPong" /> + </element> + <element id="Task.input:business-key"> + <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> + <valueString value="Parameter" /> + </extension> + <path value="Task.input" /> + <sliceName value="business-key" /> + <min value="1" /> + </element> + <element id="Task.input:correlation-key"> + <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> + <valueString value="Parameter" /> + </extension> + <path value="Task.input" /> + <sliceName value="correlation-key" /> + <max value="0"/> + </element> + <element id="Task.input:downloaded-bytes"> + <path value="Task.input"/> + <sliceName value="downloaded-bytes"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Task.input:downloaded-bytes.type"> + <path value="Task.input.type"/> + <min value="1"/> + <max value="1"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping"/> + </binding> + </element> + <element id="Task.input:downloaded-bytes.type.coding"> + <path value="Task.input.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:downloaded-bytes.type.coding.system"> + <path value="Task.input.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.input:downloaded-bytes.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:downloaded-bytes.type.coding.code"> + <path value="Task.input.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="downloaded-bytes"/> + </element> + <element id="Task.input:downloaded-bytes.value[x]"> + <path value="Task.input.value[x]"/> + <min value="1"/> + <max value="1"/> + <type> + <code value="decimal"/> + </type> + </element> + <element id="Task.input:downloaded-duration"> + <path value="Task.input"/> + <sliceName value="downloaded-duration"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Task.input:downloaded-duration.type"> + <path value="Task.input.type"/> + <min value="1"/> + <max value="1"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping"/> + </binding> + </element> + <element id="Task.input:downloaded-duration.type.coding"> + <path value="Task.input.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:downloaded-duration.type.coding.system"> + <path value="Task.input.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.input:downloaded-duration.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:downloaded-duration.type.coding.code"> + <path value="Task.input.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="downloaded-duration"/> + </element> + <element id="Task.input:downloaded-duration.value[x]"> + <path value="Task.input.value[x]"/> + <min value="1"/> + <max value="1"/> + <type> + <code value="Duration"/> + </type> + </element> + </differential> +</StructureDefinition> \ No newline at end of file diff --git a/src/main/resources/fhir/StructureDefinition/dsf-task-ping.xml b/src/main/resources/fhir/StructureDefinition/dsf-task-ping.xml index 89346167..03719e8e 100644 --- a/src/main/resources/fhir/StructureDefinition/dsf-task-ping.xml +++ b/src/main/resources/fhir/StructureDefinition/dsf-task-ping.xml @@ -31,7 +31,7 @@ </extension> <path value="Task.input" /> <min value="4" /> - <max value="4" /> + <max value="6" /> </element> <element id="Task.input:message-name"> <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> @@ -83,6 +83,12 @@ <min value="1" /> <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping" /> </element> + <element id="Task.input:endpoint-identifier.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> <element id="Task.input:endpoint-identifier.type.coding.code"> <path value="Task.input.type.coding.code" /> <min value="1" /> @@ -117,6 +123,175 @@ <path value="Task.input.value[x].identifier.value" /> <min value="1" /> </element> + <element id="Task.input:download-resource-size-bytes"> + <path value="Task.input"/> + <sliceName value="download-resource-size-bytes"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Task.input:download-resource-size-bytes.type"> + <path value="Task.input.type"/> + <min value="1"/> + <max value="1"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping"/> + </binding> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding"> + <path value="Task.input.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding.system"> + <path value="Task.input.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding.code"> + <path value="Task.input.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="download-resource-size-bytes"/> + </element> + <element id="Task.input:download-resource-size-bytes.value[x]"> + <path value="Task.input.value[x]"/> + <min value="1"/> + <max value="1"/> + <type> + <code value="decimal"/> + </type> + </element> + <element id="Task.input:download-resource-reference"> + <path value="Task.input"/> + <sliceName value="download-resource-reference"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Task.input:download-resource-reference.type"> + <path value="Task.input.type"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping|#{version}"/> + </binding> + </element> + <element id="Task.input:download-resource-reference.type.coding"> + <path value="Task.input.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:download-resource-reference.type.coding.system"> + <path value="Task.input.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.input:download-resource-reference.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:download-resource-reference.type.coding.code"> + <path value="Task.input.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="download-resource-reference"/> + </element> + <element id="Task.input:download-resource-reference.value[x]"> + <path value="Task.input.value[x]"/> + <type> + <code value="Reference" /> + <targetProfile value="http://hl7.org/fhir/StructureDefinition/Binary" /> + </type> + </element> + <element id="Task.input:download-resource-reference.value[x].reference"> + <path value="Task.input.value[x].reference"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:download-resource-reference.value[x].type"> + <path value="Task.input.value[x].type"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="Binary"/> + </element> + <element id="Task.input:download-resource-reference.value[x].identifier"> + <path value="Task.input.value[x].identifier"/> + <max value="0"/> + </element> + <element id="Task.output:process-error"> + <path value="Task.output"/> + <sliceName value="process-error"/> + <min value="0"/> + </element> + <element id="Task.output:process-error.extension"> + <path value="Task.output.extension" /> + <slicing> + <discriminator> + <type value="value" /> + <path value="url" /> + </discriminator> + <rules value="open" /> + </slicing> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.output:process-error.extension:error-extension"> + <path value="Task.output.extension" /> + <sliceName value="error-extension" /> + <min value="1" /> + <max value="1"/> + <type> + <code value="Extension" /> + <profile value="http://dsf.dev/fhir/StructureDefinition/extension-error|#{version}" /> + </type> + </element> + <element id="Task.output:process-error.type"> + <path value="Task.output.type"/> + <min value="1"/> + <max value="1"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping|#{version}"/> + </binding> + </element> + <element id="Task.output:process-error.type.coding"> + <path value="Task.output.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.output:process-error.type.coding.system"> + <path value="Task.output.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.output:process-error.type.coding.version"> + <path value="Task.output.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.output:process-error.type.coding.code"> + <path value="Task.output.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="error"/> + </element> + <element id="Task.output:process-error.value[x]"> + <path value="Task.output.value[x]"/> + <type> + <code value="string"/> + </type> + </element> <element id="Task.output:pong-status"> <path value="Task.output"/> <sliceName value="pong-status"/> @@ -157,6 +332,12 @@ <min value="1"/> <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> </element> + <element id="Task.output:pong-status.type.coding.version"> + <path value="Task.output.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> <element id="Task.output:pong-status.type.coding.code"> <path value="Task.output.type.coding.code"/> <min value="1"/> diff --git a/src/main/resources/fhir/StructureDefinition/dsf-task-pong.xml b/src/main/resources/fhir/StructureDefinition/dsf-task-pong.xml index 225c13bb..e1ef4e93 100644 --- a/src/main/resources/fhir/StructureDefinition/dsf-task-pong.xml +++ b/src/main/resources/fhir/StructureDefinition/dsf-task-pong.xml @@ -31,7 +31,6 @@ </extension> <path value="Task.input" /> <min value="3" /> - <max value="3" /> </element> <element id="Task.input:message-name"> <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> @@ -60,5 +59,220 @@ <sliceName value="correlation-key" /> <min value="1" /> </element> + <element id="Task.input:download-resource-reference"> + <path value="Task.input"/> + <sliceName value="download-resource-reference"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Task.input:download-resource-reference.type"> + <path value="Task.input.type"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping|#{version}"/> + </binding> + </element> + <element id="Task.input:download-resource-reference.type.coding"> + <path value="Task.input.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:download-resource-reference.type.coding.system"> + <path value="Task.input.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.input:download-resource-reference.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:download-resource-reference.type.coding.code"> + <path value="Task.input.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="download-resource-reference"/> + </element> + <element id="Task.input:download-resource-reference.value[x]"> + <path value="Task.input.value[x]"/> + <type> + <code value="Reference" /> + <targetProfile value="http://hl7.org/fhir/StructureDefinition/Binary" /> + </type> + </element> + <element id="Task.input:download-resource-reference.value[x].reference"> + <path value="Task.input.value[x].reference"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:download-resource-reference.value[x].type"> + <path value="Task.input.value[x].type"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="Binary"/> + </element> + <element id="Task.input:download-resource-reference.value[x].identifier"> + <path value="Task.input.value[x].identifier"/> + <max value="0"/> + </element> + <element id="Task.input:downloaded-bytes"> + <path value="Task.input"/> + <sliceName value="downloaded-bytes"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Task.input:downloaded-bytes.type"> + <path value="Task.input.type"/> + <min value="1"/> + <max value="1"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping"/> + </binding> + </element> + <element id="Task.input:downloaded-bytes.type.coding"> + <path value="Task.input.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:downloaded-bytes.type.coding.system"> + <path value="Task.input.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.input:downloaded-bytes.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:downloaded-bytes.type.coding.code"> + <path value="Task.input.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="downloaded-bytes"/> + </element> + <element id="Task.input:downloaded-bytes.value[x]"> + <path value="Task.input.value[x]"/> + <min value="1"/> + <max value="1"/> + <type> + <code value="decimal"/> + </type> + </element> + <element id="Task.input:downloaded-duration"> + <path value="Task.input"/> + <sliceName value="downloaded-duration"/> + <min value="0"/> + <max value="1"/> + </element> + <element id="Task.input:downloaded-duration.type"> + <path value="Task.input.type"/> + <min value="1"/> + <max value="1"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping"/> + </binding> + </element> + <element id="Task.input:downloaded-duration.type.coding"> + <path value="Task.input.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:downloaded-duration.type.coding.system"> + <path value="Task.input.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.input:downloaded-duration.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:downloaded-duration.type.coding.code"> + <path value="Task.input.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="downloaded-duration"/> + </element> + <element id="Task.input:downloaded-duration.value[x]"> + <path value="Task.input.value[x]"/> + <min value="1"/> + <max value="1"/> + <type> + <code value="Duration"/> + </type> + </element> + <element id="Task.input:process-error"> + <path value="Task.input"/> + <sliceName value="process-error"/> + <min value="0"/> + </element> + <element id="Task.input:process-error.extension"> + <path value="Task.input.extension" /> + <slicing> + <discriminator> + <type value="value" /> + <path value="url" /> + </discriminator> + <rules value="open" /> + </slicing> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:process-error.extension:error-extension"> + <path value="Task.input.extension" /> + <sliceName value="process-error-extension" /> + <min value="1" /> + <max value="1"/> + <type> + <code value="Extension" /> + <profile value="http://dsf.dev/fhir/StructureDefinition/extension-error|#{version}" /> + </type> + </element> + <element id="Task.input:process-error.type"> + <path value="Task.input.type"/> + <min value="1"/> + <max value="1"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping|#{version}"/> + </binding> + </element> + <element id="Task.input:process-error.type.coding"> + <path value="Task.input.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.input:process-error.type.coding.system"> + <path value="Task.input.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.input:process-error.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:process-error.type.coding.code"> + <path value="Task.input.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="error"/> + </element> + <element id="Task.input:process-error.value[x]"> + <path value="Task.input.value[x]"/> + <type> + <code value="string"/> + </type> + </element> </differential> </StructureDefinition> \ No newline at end of file diff --git a/src/main/resources/fhir/StructureDefinition/dsf-task-start-ping-autostart.xml b/src/main/resources/fhir/StructureDefinition/dsf-task-start-ping-autostart.xml index 0683d5a6..2cbb4f0f 100644 --- a/src/main/resources/fhir/StructureDefinition/dsf-task-start-ping-autostart.xml +++ b/src/main/resources/fhir/StructureDefinition/dsf-task-start-ping-autostart.xml @@ -30,8 +30,8 @@ <valueString value="Parameter" /> </extension> <path value="Task.input" /> - <min value="1" /> - <max value="4" /> + <min value="2" /> + <max value="6" /> </element> <element id="Task.input:message-name"> <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> @@ -81,6 +81,12 @@ <min value="1" /> <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping" /> </element> + <element id="Task.input:target-endpoints.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> <element id="Task.input:target-endpoints.type.coding.code"> <path value="Task.input.type.coding.code" /> <min value="1" /> @@ -121,6 +127,12 @@ <min value="1" /> <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping" /> </element> + <element id="Task.input:timer-interval.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> <element id="Task.input:timer-interval.type.coding.code"> <path value="Task.input.type.coding.code" /> <min value="1" /> @@ -138,5 +150,92 @@ <expression value="matches('^P(?:([0-9]+)Y)?(?:([0-9]+)M)?(?:([0-9]+)D)?(T(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?$')" /> </constraint> </element> + <element id="Task.input:pong-timeout-duration"> + <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> + <valueString value="Parameter" /> + </extension> + <path value="Task.input" /> + <sliceName value="pong-timeout-duration" /> + <min value="0" /> + <max value="1" /> + </element> + <element id="Task.input:pong-timeout-duration.type"> + <path value="Task.input.type" /> + <binding> + <extension url="http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName"> + <valueString value="TaskInputParameterType" /> + </extension> + <strength value="required" /> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping|#{version}" /> + </binding> + </element> + <element id="Task.input:pong-timeout-duration.type.coding"> + <path value="Task.input.type.coding" /> + <min value="1" /> + <max value="1" /> + </element> + <element id="Task.input:pong-timeout-duration.type.coding.system"> + <path value="Task.input.type.coding.system" /> + <min value="1" /> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping" /> + </element> + <element id="Task.input:pong-timeout-duration.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:pong-timeout-duration.type.coding.code"> + <path value="Task.input.type.coding.code" /> + <min value="1" /> + <fixedCode value="pong-timeout-duration" /> + </element> + <element id="Task.input:pong-timeout-duration.value[x]"> + <path value="Task.input.value[x]" /> + <type> + <code value="string" /> + </type> + <constraint> + <key value="pong-timeout-regex" /> + <severity value="error" /> + <human value="Must be ISO 8601 time duration pattern" /> + <expression value="matches('^P(?:([0-9]+)Y)?(?:([0-9]+)M)?(?:([0-9]+)D)?(T(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?$')" /> + </constraint> + </element> + <element id="Task.input:download-resource-size-bytes"> + <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> + <valueString value="Parameter" /> + </extension> + <path value="Task.input" /> + <sliceName value="download-resource-size-bytes" /> + <min value="1" /> + <max value="1" /> + </element> + <element id="Task.input:download-resource-size-bytes.type"> + <path value="Task.input.type" /> + <binding> + <extension url="http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName"> + <valueString value="TaskInputParameterType" /> + </extension> + <strength value="required" /> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping|#{version}" /> + </binding> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding.system"> + <path value="Task.input.type.coding.system" /> + <min value="1" /> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping" /> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding.code"> + <path value="Task.input.type.coding.code" /> + <min value="1" /> + <fixedCode value="download-resource-size-bytes" /> + </element> </differential> </StructureDefinition> \ No newline at end of file diff --git a/src/main/resources/fhir/StructureDefinition/dsf-task-start-ping.xml b/src/main/resources/fhir/StructureDefinition/dsf-task-start-ping.xml index cf4363e7..f868956e 100644 --- a/src/main/resources/fhir/StructureDefinition/dsf-task-start-ping.xml +++ b/src/main/resources/fhir/StructureDefinition/dsf-task-start-ping.xml @@ -31,7 +31,7 @@ </extension> <path value="Task.input" /> <min value="1" /> - <max value="3" /> + <max value="5" /> </element> <element id="Task.input:message-name"> <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> @@ -81,6 +81,12 @@ <min value="1" /> <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping" /> </element> + <element id="Task.input:target-endpoints.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> <element id="Task.input:target-endpoints.type.coding.code"> <path value="Task.input.type.coding.code" /> <min value="1" /> @@ -92,6 +98,158 @@ <code value="string" /> </type> </element> + <element id="Task.input:download-resource-size-bytes"> + <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> + <valueString value="Parameter" /> + </extension> + <path value="Task.input" /> + <sliceName value="download-resource-size-bytes" /> + <min value="0" /> + <max value="1" /> + </element> + <element id="Task.input:download-resource-size-bytes.type"> + <path value="Task.input.type" /> + <binding> + <extension url="http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName"> + <valueString value="TaskInputParameterType" /> + </extension> + <strength value="required" /> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping|#{version}" /> + </binding> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding.system"> + <path value="Task.input.type.coding.system" /> + <min value="1" /> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping" /> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:download-resource-size-bytes.type.coding.code"> + <path value="Task.input.type.coding.code" /> + <min value="1" /> + <fixedCode value="download-resource-size-bytes" /> + </element> + <element id="Task.input:pong-timeout-duration"> + <extension url="http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"> + <valueString value="Parameter" /> + </extension> + <path value="Task.input" /> + <sliceName value="pong-timeout-duration" /> + <min value="0" /> + <max value="1" /> + </element> + <element id="Task.input:pong-timeout-duration.type"> + <path value="Task.input.type" /> + <binding> + <extension url="http://hl7.org/fhir/StructureDefinition/elementdefinition-bindingName"> + <valueString value="TaskInputParameterType" /> + </extension> + <strength value="required" /> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping|#{version}" /> + </binding> + </element> + <element id="Task.input:pong-timeout-duration.type.coding"> + <path value="Task.input.type.coding" /> + <min value="1" /> + <max value="1" /> + </element> + <element id="Task.input:pong-timeout-duration.type.coding.system"> + <path value="Task.input.type.coding.system" /> + <min value="1" /> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping" /> + </element> + <element id="Task.input:pong-timeout-duration.type.coding.version"> + <path value="Task.input.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.input:pong-timeout-duration.type.coding.code"> + <path value="Task.input.type.coding.code" /> + <min value="1" /> + <fixedCode value="pong-timeout-duration" /> + </element> + <element id="Task.input:pong-timeout-duration.value[x]"> + <path value="Task.input.value[x]" /> + <type> + <code value="string" /> + </type> + <constraint> + <key value="pong-timeout-regex" /> + <severity value="error" /> + <human value="Must be ISO 8601 time duration pattern" /> + <expression value="matches('^P(?:([0-9]+)Y)?(?:([0-9]+)M)?(?:([0-9]+)D)?(T(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)(?:[.,]([0-9]{0,9}))?S)?)?$')" /> + </constraint> + </element> + <element id="Task.output:process-error"> + <path value="Task.output"/> + <sliceName value="process-error"/> + <min value="0"/> + </element> + <element id="Task.output:process-error.extension"> + <path value="Task.output.extension" /> + <slicing> + <discriminator> + <type value="value" /> + <path value="url" /> + </discriminator> + <rules value="open" /> + </slicing> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.output:process-error.extension:error-extension"> + <path value="Task.output.extension" /> + <sliceName value="error-extension" /> + <min value="1" /> + <max value="1"/> + <type> + <code value="Extension" /> + <profile value="http://dsf.dev/fhir/StructureDefinition/extension-error|#{version}" /> + </type> + </element> + <element id="Task.output:process-error.type"> + <path value="Task.output.type"/> + <min value="1"/> + <max value="1"/> + <binding> + <strength value="required"/> + <valueSet value="http://dsf.dev/fhir/ValueSet/ping|#{version}"/> + </binding> + </element> + <element id="Task.output:process-error.type.coding"> + <path value="Task.output.type.coding"/> + <min value="1"/> + <max value="1"/> + </element> + <element id="Task.output:process-error.type.coding.system"> + <path value="Task.output.type.coding.system"/> + <min value="1"/> + <max value="1"/> + <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> + </element> + <element id="Task.output:process-error.type.coding.version"> + <path value="Task.output.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> + <element id="Task.output:process-error.type.coding.code"> + <path value="Task.output.type.coding.code"/> + <min value="1"/> + <max value="1"/> + <fixedCode value="error"/> + </element> + <element id="Task.output:process-error.value[x]"> + <path value="Task.output.value[x]"/> + <type> + <code value="string"/> + </type> + </element> <element id="Task.output:ping-status"> <path value="Task.output"/> <sliceName value="ping-status"/> @@ -132,6 +290,12 @@ <min value="1"/> <fixedUri value="http://dsf.dev/fhir/CodeSystem/ping"/> </element> + <element id="Task.output:ping-status.type.coding.version"> + <path value="Task.output.type.coding.version"/> + <min value="1"/> + <max value="1"/> + <fixedString value="#{version}"/> + </element> <element id="Task.output:ping-status.type.coding.code"> <path value="Task.output.type.coding.code"/> <min value="1"/> diff --git a/src/main/resources/fhir/Task/dsf-task-start-ping-autostart.xml b/src/main/resources/fhir/Task/dsf-task-start-ping-autostart.xml index 60d7e8f5..c74ab172 100644 --- a/src/main/resources/fhir/Task/dsf-task-start-ping-autostart.xml +++ b/src/main/resources/fhir/Task/dsf-task-start-ping-autostart.xml @@ -38,19 +38,41 @@ <input> <type> <coding> - <system value="http://dsf.dev/fhir/CodeSystem/ping"></system> - <code value="target-endpoints"></code> + <system value="http://dsf.dev/fhir/CodeSystem/ping"/> + <version value="#{version}"/> + <code value="target-endpoints"/> </coding> </type> - <valueString value="Endpoint?status=active&identifier=http://dsf.dev/sid/endpoint-identifier|"></valueString> + <valueString value="Endpoint?status=active&identifier=http://dsf.dev/sid/endpoint-identifier|"/> </input> <input> <type> <coding> - <system value="http://dsf.dev/fhir/CodeSystem/ping"></system> - <code value="timer-interval"></code> + <system value="http://dsf.dev/fhir/CodeSystem/ping"/> + <version value="#{version}"/> + <code value="timer-interval"/> </coding> </type> - <valueString value="PT24H"></valueString> + <valueString value="PT24H"/> </input> + <input> + <type> + <coding> + <system value="http://dsf.dev/fhir/CodeSystem/ping"/> + <version value="#{version}"/> + <code value="download-resource-size-bytes"/> + </coding> + </type> + <valueDecimal value="10000000"/> + </input> + <input> + <type> + <coding> + <system value="http://dsf.dev/fhir/CodeSystem/ping"/> + <version value="#{version}"/> + <code value="pong-timeout-duration"/> + </coding> + </type> + <valueString value="PT30S"/> + </input> </Task> diff --git a/src/main/resources/fhir/Task/dsf-task-start-ping.xml b/src/main/resources/fhir/Task/dsf-task-start-ping.xml index d8ef908b..97741f32 100644 --- a/src/main/resources/fhir/Task/dsf-task-start-ping.xml +++ b/src/main/resources/fhir/Task/dsf-task-start-ping.xml @@ -1,47 +1,68 @@ <Task xmlns="http://hl7.org/fhir"> - <meta> - <profile value="http://dsf.dev/fhir/StructureDefinition/task-start-ping|#{version}"/> - </meta> - <identifier> - <system value="http://dsf.dev/sid/task-identifier"/> - <value value="http://dsf.dev/bpe/Process/ping/#{version}/task-start-ping"/> - </identifier> - <instantiatesCanonical value="http://dsf.dev/bpe/Process/ping|#{version}"/> - <status value="draft"/> - <intent value="order"/> - <authoredOn value="#{date}"/> - <requester> - <type value="Organization"/> - <identifier> - <system value="http://dsf.dev/sid/organization-identifier"/> - <value value="#{organization}"/> - </identifier> - </requester> - <restriction> - <recipient> - <type value="Organization"/> - <identifier> - <system value="http://dsf.dev/sid/organization-identifier"/> - <value value="#{organization}"/> - </identifier> - </recipient> - </restriction> - <input> - <type> - <coding> - <system value="http://dsf.dev/fhir/CodeSystem/bpmn-message"/> - <code value="message-name"/> - </coding> - </type> - <valueString value="startPing"/> - </input> - <input> - <type> - <coding> - <system value="http://dsf.dev/fhir/CodeSystem/ping"></system> - <code value="target-endpoints"></code> - </coding> - </type> - <valueString value="Endpoint?status=active&identifier=http://dsf.dev/sid/endpoint-identifier|"></valueString> - </input> + <meta> + <profile value="http://dsf.dev/fhir/StructureDefinition/task-start-ping|#{version}"/> + </meta> + <identifier> + <system value="http://dsf.dev/sid/task-identifier"/> + <value value="http://dsf.dev/bpe/Process/ping/#{version}/task-start-ping"/> + </identifier> + <instantiatesCanonical value="http://dsf.dev/bpe/Process/ping|#{version}"/> + <status value="draft"/> + <intent value="order"/> + <authoredOn value="#{date}"/> + <requester> + <type value="Organization"/> + <identifier> + <system value="http://dsf.dev/sid/organization-identifier"/> + <value value="#{organization}"/> + </identifier> + </requester> + <restriction> + <recipient> + <type value="Organization"/> + <identifier> + <system value="http://dsf.dev/sid/organization-identifier"/> + <value value="#{organization}"/> + </identifier> + </recipient> + </restriction> + <input> + <type> + <coding> + <system value="http://dsf.dev/fhir/CodeSystem/bpmn-message"/> + <code value="message-name"/> + </coding> + </type> + <valueString value="startPing"/> + </input> + <input> + <type> + <coding> + <system value="http://dsf.dev/fhir/CodeSystem/ping"/> + <version value="#{version}"/> + <code value="target-endpoints"/> + </coding> + </type> + <valueString value="Endpoint?status=active&identifier=http://dsf.dev/sid/endpoint-identifier|"/> + </input> + <input> + <type> + <coding> + <system value="http://dsf.dev/fhir/CodeSystem/ping"/> + <version value="#{version}"/> + <code value="download-resource-size-bytes"/> + </coding> + </type> + <valueDecimal value="1000000"/> + </input> + <input> + <type> + <coding> + <system value="http://dsf.dev/fhir/CodeSystem/ping"/> + <version value="#{version}"/> + <code value="pong-timeout-duration"/> + </coding> + </type> + <valueString value="PT30S"/> + </input> </Task> diff --git a/src/main/resources/fhir/ValueSet/dsf-network-speed-units.xml b/src/main/resources/fhir/ValueSet/dsf-network-speed-units.xml new file mode 100644 index 00000000..dc170e51 --- /dev/null +++ b/src/main/resources/fhir/ValueSet/dsf-network-speed-units.xml @@ -0,0 +1,49 @@ +<ValueSet xmlns="http://hl7.org/fhir"> + <meta> + <tag> + <system + value="http://dsf.dev/fhir/CodeSystem/read-access-tag" /> + <code value="ALL" /> + </tag> + </meta> + <url value="http://dsf.dev/fhir/ValueSet/ping-units" /> + <!-- version managed by bpe --> + <version value="#{version}" /> + <name value="DSF_Ping_Units" /> + <title value="DSF Ping Units" /> + <!-- status managed by bpe --> + <status value="unknown" /> + <experimental value="false" /> + <!-- date managed by bpe --> + <date value="#{date}" /> + <publisher value="DSF" /> + <description value="ValueSet with ping download speed units" /> + <immutable value="true" /> + <compose> + <include> + <system value="http://unitsofmeasure.org" /> + <version value="3.0.1"/> + <concept> + <code value="bit/s"/> + </concept> + <concept> + <code value="Kbit/s"/> + </concept> + <concept> + <code value="Mbit/s"/> + </concept> + <concept> + <code value="GBit/s"/> + </concept> + <concept> + <code value="By/s"/> + </concept> + <concept> + <code value="KBy/s"/> + </concept> + <concept> + <code value="MBy/s"/> + </concept> + </include> + </compose> +</ValueSet> \ No newline at end of file diff --git a/src/test/java/dev/dsf/bpe/PingProcessPluginDefinitionTest.java b/src/test/java/dev/dsf/bpe/PingProcessPluginDefinitionTest.java index 978fd13e..55e289ab 100644 --- a/src/test/java/dev/dsf/bpe/PingProcessPluginDefinitionTest.java +++ b/src/test/java/dev/dsf/bpe/PingProcessPluginDefinitionTest.java @@ -20,7 +20,7 @@ public void testResourceLoading() throws Exception var ping = resourcesByProcessId.get(ConstantsPing.PROCESS_NAME_FULL_PING); assertNotNull(ping); - assertEquals(9, ping.stream().filter(this::exists).count()); + assertEquals(13, ping.stream().filter(this::exists).count()); var pingAutostart = resourcesByProcessId.get(ConstantsPing.PROCESS_NAME_FULL_PING_AUTOSTART); assertNotNull(pingAutostart); @@ -28,7 +28,7 @@ public void testResourceLoading() throws Exception var pong = resourcesByProcessId.get(ConstantsPing.PROCESS_NAME_FULL_PONG); assertNotNull(pong); - assertEquals(7, pong.stream().filter(this::exists).count()); + assertEquals(10, pong.stream().filter(this::exists).count()); } private boolean exists(String file) diff --git a/src/test/java/dev/dsf/bpe/start/PingTtpsFromDic1ExampleStarter.java b/src/test/java/dev/dsf/bpe/start/PingTtpsFromDic1ExampleStarter.java index 180b015f..ceec3006 100644 --- a/src/test/java/dev/dsf/bpe/start/PingTtpsFromDic1ExampleStarter.java +++ b/src/test/java/dev/dsf/bpe/start/PingTtpsFromDic1ExampleStarter.java @@ -6,6 +6,7 @@ import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Task; +import dev.dsf.bpe.CodeSystem; import dev.dsf.bpe.ConstantsPing; import dev.dsf.bpe.PingProcessPluginDefinition; import dev.dsf.bpe.v1.constants.CodeSystems.BpmnMessage; @@ -45,8 +46,8 @@ private static Task task() "OrganizationAffiliation?primary-organization:identifier=http://dsf.dev/sid/organization-identifier|highmed.org" + "&role=http://dsf.dev/fhir/CodeSystem/organization-role|TTP" + "&_include=OrganizationAffiliation:endpoint")) - .getType().addCoding().setSystem(ConstantsPing.CODESYSTEM_DSF_PING) - .setCode(ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TARGET_ENDPOINTS); + .getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.TARGET_ENDPOINTS.getValue()); return task; } diff --git a/src/test/java/dev/dsf/bpe/start/StartAutostartPing3DicFromTtpExampleStarter.java b/src/test/java/dev/dsf/bpe/start/StartAutostartPing3DicFromTtpExampleStarter.java index f2896072..396f424e 100644 --- a/src/test/java/dev/dsf/bpe/start/StartAutostartPing3DicFromTtpExampleStarter.java +++ b/src/test/java/dev/dsf/bpe/start/StartAutostartPing3DicFromTtpExampleStarter.java @@ -6,6 +6,7 @@ import org.hl7.fhir.r4.model.StringType; import org.hl7.fhir.r4.model.Task; +import dev.dsf.bpe.CodeSystem; import dev.dsf.bpe.ConstantsPing; import dev.dsf.bpe.PingProcessPluginDefinition; import dev.dsf.bpe.v1.constants.CodeSystems.BpmnMessage; @@ -42,11 +43,10 @@ private static Task task() task.addInput().setValue(new StringType(ConstantsPing.PROFILE_DSF_TASK_START_PING_AUTOSTART_MESSAGE_NAME)) .getType().addCoding(BpmnMessage.messageName()); task.addInput().setValue(new StringType("Endpoint?identifier=http://dsf.dev/sid/endpoint-identifier|")) - .getType().addCoding().setSystem(ConstantsPing.CODESYSTEM_DSF_PING) - .setCode(ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TARGET_ENDPOINTS); - task.addInput().setValue(new StringType("PT7M")).getType().addCoding() - .setSystem(ConstantsPing.CODESYSTEM_DSF_PING) - .setCode(ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TIMER_INTERVAL); + .getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.TARGET_ENDPOINTS.getValue()); + task.addInput().setValue(new StringType("PT7M")).getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.TIMER_INTERVAL.getValue()); return task; } diff --git a/src/test/java/dev/dsf/bpe/util/PingStatusGeneratorTest.java b/src/test/java/dev/dsf/bpe/util/PingStatusGeneratorTest.java new file mode 100644 index 00000000..04d32224 --- /dev/null +++ b/src/test/java/dev/dsf/bpe/util/PingStatusGeneratorTest.java @@ -0,0 +1,60 @@ +package dev.dsf.bpe.util; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.hl7.fhir.r4.model.Element; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.Task; +import org.junit.Test; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.task.output.generator.PingStatusGenerator; +import dev.dsf.fhir.profiles.TaskProfileTest; + +public class PingStatusGeneratorTest +{ + @Test + public void updatingPongErrorsResultsInOneErrorsExtensionTest() + { + String process = "pong"; + + List<ProcessError> errors = processErrors(process); + + Task pongTask = TaskProfileTest.createValidTaskStartPingProcess(); + + PingStatusGenerator.updatePongStatusOutput(pongTask, errors); + + errors.add(new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_BINARY_POST_HTTP_UNEXPECTED, null)); + + PingStatusGenerator.updatePongStatusOutput(pongTask, errors); + + List<Extension> errorsExtensions = pongTask.getOutput().stream().map(Element::getExtension) + .map(extensions -> extensions.stream() + .filter(extension -> ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_PING_STATUS + .equals(extension.getUrl())) + .findFirst().orElse(null)) + .filter(Objects::nonNull) + .map(extension -> extension.getExtension().stream() + .filter(extension1 -> ConstantsPing.EXTENSION_URL_ERRORS.equals(extension1.getUrl())).toList()) + .reduce(new ArrayList<>(), (list1, list2) -> + { + list1.addAll(list2); + return list1; + }); + + assertEquals(1, errorsExtensions.size()); + } + + private List<ProcessError> processErrors(String process) + { + List<ProcessError> errors = new ArrayList<>(); + errors.add(new ProcessError(process, CodeSystem.DsfPingError.Concept.LOCAL_UNKNOWN, null)); + return errors; + } +} diff --git a/src/test/java/dev/dsf/fhir/profiles/TaskProfileTest.java b/src/test/java/dev/dsf/fhir/profiles/TaskProfileTest.java index 75c355aa..72a65a84 100644 --- a/src/test/java/dev/dsf/fhir/profiles/TaskProfileTest.java +++ b/src/test/java/dev/dsf/fhir/profiles/TaskProfileTest.java @@ -2,10 +2,21 @@ import static org.junit.Assert.assertEquals; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.List; +import java.util.TimeZone; import java.util.UUID; +import org.hl7.fhir.r4.model.DecimalType; import org.hl7.fhir.r4.model.Reference; import org.hl7.fhir.r4.model.ResourceType; import org.hl7.fhir.r4.model.StringType; @@ -17,11 +28,20 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.validation.ResultSeverityEnum; import ca.uhn.fhir.validation.ValidationResult; +import dev.dsf.bpe.CodeSystem; import dev.dsf.bpe.ConstantsPing; import dev.dsf.bpe.PingProcessPluginDefinition; -import dev.dsf.bpe.util.PingStatusGenerator; +import dev.dsf.bpe.ProcessError; +import dev.dsf.bpe.util.task.input.generator.DownloadResourceReferenceGenerator; +import dev.dsf.bpe.util.task.input.generator.DownloadResourceSizeGenerator; +import dev.dsf.bpe.util.task.input.generator.DownloadedBytesGenerator; +import dev.dsf.bpe.util.task.input.generator.DownloadedDurationGenerator; +import dev.dsf.bpe.util.task.input.generator.ErrorInputComponentGenerator; +import dev.dsf.bpe.util.task.output.generator.PingStatusGenerator; import dev.dsf.bpe.v1.constants.CodeSystems.BpmnMessage; import dev.dsf.bpe.v1.constants.NamingSystems.EndpointIdentifier; import dev.dsf.bpe.v1.constants.NamingSystems.OrganizationIdentifier; @@ -39,19 +59,21 @@ public class TaskProfileTest @ClassRule public static final ValidationSupportRule validationRule = new ValidationSupportRule(def.getResourceVersion(), def.getResourceReleaseDate(), - Arrays.asList("dsf-task-base-1.0.0.xml", "dsf-extension-ping-status.xml", "dsf-task-ping.xml", - "dsf-task-pong.xml", "dsf-task-start-ping.xml", "dsf-task-start-ping-autostart.xml", - "dsf-task-stop-ping-autostart.xml"), - Arrays.asList("dsf-read-access-tag-1.0.0.xml", "dsf-bpmn-message-1.0.0.xml", "dsf-ping.xml", - "dsf-ping-status.xml"), - Arrays.asList("dsf-read-access-tag-1.0.0.xml", "dsf-bpmn-message-1.0.0.xml", "dsf-ping.xml", - "dsf-ping-status.xml", "dsf-pong-status.xml")); + Arrays.asList("dsf-task-base-1.0.0.xml", "dsf-extension-error.xml", "dsf-extension-ping-status.xml", + "dsf-task-ping.xml", "dsf-task-pong.xml", "dsf-task-start-ping.xml", + "dsf-task-start-ping-autostart.xml", "dsf-task-stop-ping-autostart.xml", + "dsf-task-cleanup-pong.xml"), + Arrays.asList("dsf-read-access-tag-1.0.0.xml", "dsf-bpmn-message-1.0.0.xml", "dsf-ping-1_0.xml", + "dsf-ping.xml", "dsf-ping-status-1_0.xml", "dsf-ping-status.xml"), + Arrays.asList("dsf-read-access-tag-1.0.0.xml", "dsf-bpmn-message-1.0.0.xml", "dsf-ping-1_0.xml", + "dsf-ping.xml", "dsf-ping-status-1_0.xml", "dsf-ping-status.xml", "dsf-pong-status-1_0.xml", + "dsf-pong-status.xml", "dsf-network-speed-units.xml")); private ResourceValidator resourceValidator = new ResourceValidatorImpl(validationRule.getFhirContext(), validationRule.getValidationSupport()); @Test - public void testTaskStartAutostartProcessProfileValid() throws Exception + public void testTaskStartAutostartProcessProfileValid() { Task task = createValidTaskStartAutostartProcess(); @@ -63,14 +85,15 @@ public void testTaskStartAutostartProcessProfileValid() throws Exception } @Test - public void testTaskStartAutostartProcessProfileValidWithTargetEndpoints() throws Exception + public void testTaskStartAutostartProcessProfileValidWithTargetEndpoints() { Task task = createValidTaskStartAutostartProcess(); task.addInput() .setValue(new StringType( "Endpoint?identifier=http://dsf.dev/sid/endpoint-identifier|endpoint.target.org")) - .getType().addCoding().setSystem(ConstantsPing.CODESYSTEM_DSF_PING) - .setCode(ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TARGET_ENDPOINTS); + .getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.TARGET_ENDPOINTS.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); ValidationResult result = resourceValidator.validate(task); ValidationSupportRule.logValidationMessages(logger, result); @@ -80,12 +103,12 @@ public void testTaskStartAutostartProcessProfileValidWithTargetEndpoints() throw } @Test - public void testTaskStartAutostartProcessProfileValidTimerInterval() throws Exception + public void testTaskStartAutostartProcessProfileValidTimerInterval() { Task task = createValidTaskStartAutostartProcess(); - task.addInput().setValue(new StringType("PT24H")).getType().addCoding() - .setSystem(ConstantsPing.CODESYSTEM_DSF_PING) - .setCode(ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TIMER_INTERVAL); + task.addInput().setValue(new StringType("PT24H")).getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.TIMER_INTERVAL.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); ValidationResult result = resourceValidator.validate(task); ValidationSupportRule.logValidationMessages(logger, result); @@ -95,12 +118,12 @@ public void testTaskStartAutostartProcessProfileValidTimerInterval() throws Exce } @Test - public void testTaskStartAutostartProcessProfileNotValidTimerInterval() throws Exception + public void testTaskStartAutostartProcessProfileNotValidTimerInterval() { Task task = createValidTaskStartAutostartProcess(); task.addInput().setValue(new StringType("invalid_duration")).getType().addCoding() - .setSystem(ConstantsPing.CODESYSTEM_DSF_PING) - .setCode(ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TIMER_INTERVAL); + .setSystem(CodeSystem.DsfPing.URL).setCode(CodeSystem.DsfPing.Code.TIMER_INTERVAL.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); ValidationResult result = resourceValidator.validate(task); ValidationSupportRule.logValidationMessages(logger, result); @@ -126,11 +149,13 @@ private Task createValidTaskStartAutostartProcess() task.addInput().setValue(new StringType(ConstantsPing.PROFILE_DSF_TASK_START_PING_AUTOSTART_MESSAGE_NAME)) .getType().addCoding(BpmnMessage.messageName()); + task.addInput(DownloadResourceSizeGenerator.create(0)); + return task; } @Test - public void testTaskStopAutostartProcessProfileValid() throws Exception + public void testTaskStopAutostartProcessProfileValid() { Task task = createValidTaskStopAutostartProcess(); @@ -162,10 +187,24 @@ private Task createValidTaskStopAutostartProcess() } @Test - public void testTaskStartPingProcessProfileValid() throws Exception + public void testTaskStartPingProcessProfileValid() + { + Task task = createValidTaskStartPingProcess(); + + ValidationResult result = resourceValidator.validate(task); + ValidationSupportRule.logValidationMessages(logger, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } + + @Test + public void testTaskStartPingProcessProfileValidWithErrorMessages() { Task task = createValidTaskStartPingProcess(); + ProcessError.toTaskOutput(processErrors(4)).forEach(task::addOutput); + ValidationResult result = resourceValidator.validate(task); ValidationSupportRule.logValidationMessages(logger, result); @@ -173,15 +212,27 @@ public void testTaskStartPingProcessProfileValid() throws Exception || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); } + private List<ProcessError> processErrors(int amount) + { + List<ProcessError> errors = new ArrayList<>(); + for (int i = 0; i < amount; i++) + { + CodeSystem.DsfPingError.Concept[] concepts = CodeSystem.DsfPingError.Concept.values(); + errors.add(new ProcessError(ConstantsPing.PROCESS_NAME_PING, concepts[i % concepts.length], null)); + } + return errors; + } + @Test - public void testTaskStartPingProcessProfileValidWithTargetEndpoints() throws Exception + public void testTaskStartPingProcessProfileValidWithTargetEndpoints() { Task task = createValidTaskStartPingProcess(); task.addInput() .setValue(new StringType( "Endpoint?identifier=http://dsf.dev/sid/endpoint-identifier|endpoint.target.org")) - .getType().addCoding().setSystem(ConstantsPing.CODESYSTEM_DSF_PING) - .setCode(ConstantsPing.CODESYSTEM_DSF_PING_VALUE_TARGET_ENDPOINTS); + .getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.TARGET_ENDPOINTS.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); ValidationResult result = resourceValidator.validate(task); ValidationSupportRule.logValidationMessages(logger, result); @@ -191,7 +242,7 @@ public void testTaskStartPingProcessProfileValidWithTargetEndpoints() throws Exc } @Test - public void testTaskStartPingProcessProfileValidWithBuisnessKeyOutput() throws Exception + public void testTaskStartPingProcessProfileValidWithBuisnessKeyOutput() { Task task = createValidTaskStartPingProcess(); task.addOutput().setValue(new StringType(UUID.randomUUID().toString())).getType() @@ -235,10 +286,54 @@ public String getCorrelationKey() }; Task task = createValidTaskStartPingProcess(); - task.addOutput().setValue(new StringType(UUID.randomUUID().toString())).getType() + task.addInput().setValue(new StringType(UUID.randomUUID().toString())).getType() + .addCoding(BpmnMessage.businessKey()); + task.addOutput(PingStatusGenerator.createPingStatusOutput(target, CodeSystem.DsfPingStatus.Code.PONG_MISSING, + processErrors(5))); + + ValidationResult result = resourceValidator.validate(task); + ValidationSupportRule.logValidationMessages(logger, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } + + @Test + public void testTaskStartPingProcessProfileValidWithBusinessKeyAndPingStatusOutputWithDownloadAndUploadSpeeds() + throws Exception + { + Target target = new Target() + { + @Override + public String getOrganizationIdentifierValue() + { + return "target.org"; + } + + @Override + public String getEndpointUrl() + { + return "https://endpoint.target.org/fhir"; + } + + @Override + public String getEndpointIdentifierValue() + { + return "endpoint.target.org"; + } + + @Override + public String getCorrelationKey() + { + return UUID.randomUUID().toString(); + } + }; + + Task task = createValidTaskStartPingProcess(); + task.addInput().setValue(new StringType(UUID.randomUUID().toString())).getType() .addCoding(BpmnMessage.businessKey()); - task.addOutput(new PingStatusGenerator().createPingStatusOutput(target, - ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_NOT_REACHABLE, "some error message")); + task.addOutput(createPingStatusOutput(target, CodeSystem.DsfPingStatus.Code.PONG_RECEIVED, BigDecimal.ZERO, + BigDecimal.ZERO, CodeSystem.DsfPingUnits.Code.bps)); ValidationResult result = resourceValidator.validate(task); ValidationSupportRule.logValidationMessages(logger, result); @@ -248,7 +343,7 @@ public String getCorrelationKey() } @Test - public void testTaskStartPingProcessProfileNotValid1() throws Exception + public void testTaskStartPingProcessProfileNotValid1() { Task task = createValidTaskStartPingProcess(); task.setInstantiatesCanonical("http://dsf.dev/bpe/Process/ping/0.1.0"); // not valid @@ -261,7 +356,7 @@ public void testTaskStartPingProcessProfileNotValid1() throws Exception } @Test - public void testTaskStartPingProcessProfileNotValid2() throws Exception + public void testTaskStartPingProcessProfileNotValid2() { Task task = createValidTaskStartPingProcess(); task.setIntent(TaskIntent.FILLERORDER); @@ -274,7 +369,7 @@ public void testTaskStartPingProcessProfileNotValid2() throws Exception } @Test - public void testTaskStartPingProcessProfileNotValid3() throws Exception + public void testTaskStartPingProcessProfileNotValid3() { Task task = createValidTaskStartPingProcess(); task.setAuthoredOn(null); @@ -286,7 +381,7 @@ public void testTaskStartPingProcessProfileNotValid3() throws Exception || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); } - private Task createValidTaskStartPingProcess() + public static Task createValidTaskStartPingProcess() { Task task = new Task(); task.getMeta().addProfile(ConstantsPing.PROFILE_DSF_TASK_START_PING); @@ -301,12 +396,15 @@ private Task createValidTaskStartPingProcess() task.addInput().setValue(new StringType(ConstantsPing.PROFILE_DSF_TASK_START_PING_MESSAGE_NAME)).getType() .addCoding(BpmnMessage.messageName()); + task.addInput().setValue(new DecimalType(1)).getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.DOWNLOAD_RESOURCE_SIZE_BYTES.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); return task; } @Test - public void testTaskPingValid() throws Exception + public void testTaskPingValid() { Task task = createValidTaskPing(); @@ -347,8 +445,7 @@ public String getCorrelationKey() } }; Task task = createValidTaskPing(); - task.addOutput(new PingStatusGenerator().createPongStatusOutput(target, - ConstantsPing.CODESYSTEM_DSF_PING_STATUS_VALUE_PONG_SEND)); + task.addOutput(createPongStatusOutput(target, CodeSystem.DsfPingStatus.Code.PONG_SENT)); ValidationResult result = resourceValidator.validate(task); ValidationSupportRule.logValidationMessages(logger, result); @@ -357,7 +454,50 @@ public String getCorrelationKey() || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); } - private Task createValidTaskPing() + @Test + public void testTaskPingValidWithPingStatusOutputAndDownloadResourceSizeAndDownloadResourceReference() + throws Exception + { + Target target = new Target() + { + @Override + public String getOrganizationIdentifierValue() + { + return "target.org"; + } + + @Override + public String getEndpointUrl() + { + return "https://endpoint.target.org/fhir"; + } + + @Override + public String getEndpointIdentifierValue() + { + return "endpoint.target.org"; + } + + @Override + public String getCorrelationKey() + { + return UUID.randomUUID().toString(); + } + }; + Task task = createValidTaskPing(); + task.addOutput(createPongStatusOutput(target, CodeSystem.DsfPingStatus.Code.PONG_SENT)); + + task.addInput(DownloadResourceSizeGenerator.create(1000)); + task.addInput(DownloadResourceReferenceGenerator.create("https://test.endpoint.org/fhir/Binary")); + + ValidationResult result = resourceValidator.validate(task); + ValidationSupportRule.logValidationMessages(logger, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } + + public static Task createValidTaskPing() { Task task = new Task(); task.getMeta().addProfile(ConstantsPing.PROFILE_DSF_TASK_PING); @@ -379,17 +519,64 @@ private Task createValidTaskPing() task.addInput() .setValue(new Reference().setType(ResourceType.Endpoint.name()) .setIdentifier(EndpointIdentifier.withValue("endpoint.target.org"))) - .getType().addCoding().setSystem(ConstantsPing.CODESYSTEM_DSF_PING) - .setCode(ConstantsPing.CODESYSTEM_DSF_PING_VALUE_ENDPOINT_IDENTIFIER); + .getType().addCoding().setSystem(CodeSystem.DsfPing.URL) + .setCode(CodeSystem.DsfPing.Code.ENDPOINT_IDENTIFIER.getValue()) + .setVersion(PingProcessPluginDefinition.RESOURCE_VERSION); return task; } @Test - public void testTaskPongValid() throws Exception + public void testTaskPongValid() + { + Task task = createValidTaskPong(); + + ValidationResult result = resourceValidator.validate(task); + ValidationSupportRule.logValidationMessages(logger, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } + + @Test + public void testTaskPongValidWithReferenceAndDownloadedDurationMillisAndDownloadedBytesPresent() + { + Task task = createValidTaskPong(); + + task.addInput(DownloadResourceReferenceGenerator.create("https://test.endpoint.org/fhir/Binary")); + task.addInput(DownloadedBytesGenerator.create(1000)); + task.addInput(DownloadedDurationGenerator.create(Duration.ofMillis(1000))); + + ValidationResult result = resourceValidator.validate(task); + ValidationSupportRule.logValidationMessages(logger, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } + + @Test + public void testTaskPongValidWithMultipleErrorMessages() { Task task = createValidTaskPong(); + task.addInput(DownloadResourceReferenceGenerator.create("https://test.endpoint.org/fhir/Binary")); + task.addInput(DownloadedBytesGenerator.create(1000)); + task.addInput(DownloadedDurationGenerator.create(Duration.ofMillis(1000))); + task.addInput(ErrorInputComponentGenerator.create(processErrors(1).get(0))); + task.addInput(ErrorInputComponentGenerator.create(processErrors(1).get(0))); + + ValidationResult result = resourceValidator.validate(task); + ValidationSupportRule.logValidationMessages(logger, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } + + @Test + public void testTaskCleanupPongValid() + { + Task task = createValidTaskCleanupPong(); + ValidationResult result = resourceValidator.validate(task); ValidationSupportRule.logValidationMessages(logger, result); @@ -419,4 +606,109 @@ private Task createValidTaskPong() return task; } + + private Task createValidTaskCleanupPong() + { + Task task = new Task(); + task.getMeta().addProfile(ConstantsPing.PROFILE_DSF_TASK_CLEANUP_PONG); + task.setInstantiatesCanonical( + ConstantsPing.PROFILE_DSF_TASK_CLEANUP_PONG_PROCESS_URI + "|" + def.getResourceVersion()); + task.setStatus(TaskStatus.REQUESTED); + task.setIntent(TaskIntent.ORDER); + task.setAuthoredOn(new Date()); + task.getRequester().setType(ResourceType.Organization.name()) + .setIdentifier(OrganizationIdentifier.withValue("DIC 1")); + task.getRestriction().addRecipient().setType(ResourceType.Organization.name()) + .setIdentifier(OrganizationIdentifier.withValue("TTP")); + + task.addInput().setValue(new StringType(ConstantsPing.PROFILE_DSF_TASK_CLEANUP_PONG_MESSAGE_NAME)).getType() + .addCoding(BpmnMessage.messageName()); + task.addInput().setValue(new StringType(UUID.randomUUID().toString())).getType() + .addCoding(BpmnMessage.businessKey()); + + task.addInput(DownloadedBytesGenerator.create(1000)); + task.addInput(DownloadedDurationGenerator.create(Duration.ofMillis(1000))); + return task; + } + + @Test + public void testDraftTaskStartPingValid() throws IOException + { + FhirContext ctx = FhirContext.forR4(); + InputStream fileInputStream = getClass().getClassLoader() + .getResourceAsStream("fhir/Task/dsf-task-start-ping.xml"); + String xml = new String(fileInputStream.readAllBytes()); + xml = fillPlaceholders(xml); + fileInputStream.close(); + + IParser parser = ctx.newXmlParser(); + Task task = parser.parseResource(Task.class, xml); + + ValidationResult result = resourceValidator.validate(task); + ValidationSupportRule.logValidationMessages(logger, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } + + @Test + public void testDraftTaskStartPingAutostartValid() throws IOException + { + FhirContext ctx = FhirContext.forR4(); + InputStream fileInputStream = getClass().getClassLoader() + .getResourceAsStream("fhir/Task/dsf-task-start-ping-autostart.xml"); + String xml = new String(fileInputStream.readAllBytes()); + xml = fillPlaceholders(xml); + fileInputStream.close(); + + IParser parser = ctx.newXmlParser(); + Task task = parser.parseResource(Task.class, xml); + + ValidationResult result = resourceValidator.validate(task); + ValidationSupportRule.logValidationMessages(logger, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } + + @Test + public void testDraftTaskStopPingAutostartValid() throws IOException + { + FhirContext ctx = FhirContext.forR4(); + InputStream fileInputStream = getClass().getClassLoader() + .getResourceAsStream("fhir/Task/dsf-task-stop-ping-autostart.xml"); + String xml = new String(fileInputStream.readAllBytes()); + xml = fillPlaceholders(xml); + fileInputStream.close(); + + IParser parser = ctx.newXmlParser(); + Task task = parser.parseResource(Task.class, xml); + + ValidationResult result = resourceValidator.validate(task); + ValidationSupportRule.logValidationMessages(logger, result); + + assertEquals(0, result.getMessages().stream().filter(m -> ResultSeverityEnum.ERROR.equals(m.getSeverity()) + || ResultSeverityEnum.FATAL.equals(m.getSeverity())).count()); + } + + private String fillPlaceholders(String xml) + { + DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + xml = xml.replaceAll("#\\{version}", def.getResourceVersion()); + xml = xml.replaceAll("#\\{date}", + dtf.format(LocalDate.ofInstant(Instant.now(), TimeZone.getDefault().toZoneId()))); + return xml; + } + + private Task.TaskOutputComponent createPingStatusOutput(Target target, CodeSystem.DsfPingStatus.Code statusCode, + BigDecimal downloadSpeed, BigDecimal uploadSpeed, CodeSystem.DsfPingUnits.Code unit) + { + return PingStatusGenerator.createPingStatusOutput(target, statusCode, null, downloadSpeed, unit, uploadSpeed, + unit); + } + + private Task.TaskOutputComponent createPongStatusOutput(Target target, CodeSystem.DsfPingStatus.Code statusCode) + { + return PingStatusGenerator.createPongStatusOutput(target, statusCode, null); + } } diff --git a/src/test/java/dev/dsf/library/InputStreamTest.java b/src/test/java/dev/dsf/library/InputStreamTest.java new file mode 100644 index 00000000..8ca977ac --- /dev/null +++ b/src/test/java/dev/dsf/library/InputStreamTest.java @@ -0,0 +1,51 @@ +package dev.dsf.library; + +import static org.junit.Assert.assertThrows; + +import java.io.ByteArrayInputStream; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Random; + +import org.junit.Test; + +public class InputStreamTest +{ + @Test + public void testSkipNBytesSuccessWhenAllSkipped() throws IOException + { + int length = 1000; + int toSkip = length; + byte[] data = randomData(length); + InputStream in = new ByteArrayInputStream(data); + in.skipNBytes(toSkip); + } + + @Test + public void testSkipNBytesSuccessWhenSomeSkipped() throws IOException + { + int length = 1000; + int toSkip = length / 2; + byte[] data = randomData(length); + InputStream in = new ByteArrayInputStream(data); + in.skipNBytes(toSkip); + } + + @Test + public void testSkipNBytesFailOnSkipTooMany() throws IOException + { + int length = 1000; + int toSkip = length + 1; + byte[] data = randomData(length); + InputStream in = new ByteArrayInputStream(data); + assertThrows(EOFException.class, () -> in.skipNBytes(toSkip)); + } + + private byte[] randomData(int length) + { + byte[] data = new byte[length]; + new Random().nextBytes(data); + return data; + } +} diff --git a/src/test/java/dev/dsf/library/ProcessErrorTest.java b/src/test/java/dev/dsf/library/ProcessErrorTest.java new file mode 100644 index 00000000..6b870867 --- /dev/null +++ b/src/test/java/dev/dsf/library/ProcessErrorTest.java @@ -0,0 +1,77 @@ +package dev.dsf.library; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +import org.hl7.fhir.r4.model.Coding; +import org.hl7.fhir.r4.model.Extension; +import org.hl7.fhir.r4.model.StringType; +import org.hl7.fhir.r4.model.UrlType; +import org.junit.Test; + +import dev.dsf.bpe.CodeSystem; +import dev.dsf.bpe.ConstantsPing; +import dev.dsf.bpe.ProcessError; + +public class ProcessErrorTest +{ + private static final String testString = "foo"; + private static final String testProcess = ConstantsPing.PROCESS_NAME_PING; + private static final CodeSystem.DsfPingError.Concept testConcept = CodeSystem.DsfPingError.Concept.SEND_MESSAGE_HTTP_401; + + @Test + public void ExtensionToErrorTest() + { + ProcessError expected = new ProcessError(testProcess, testConcept, testString); + assertEquals(expected, ProcessError.toError(getExtensionFull(), testProcess)); + } + + @Test + public void ExtensionWithoutFixUrlToErrorTest() + { + ProcessError expected = new ProcessError(testProcess, testConcept, null); + assertEquals(expected, ProcessError.toError(getExtensionMissingFixUrl(), testProcess)); + } + + @Test + public void ExtensionWithoutErrorToErrorTest() + { + assertThrows(NullPointerException.class, () -> ProcessError.toError(getExtensionMissingError(), testProcess)); + } + + + private Extension getExtensionFull() + { + Extension extension = new Extension(); + extension.setUrl(ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_ERROR); + + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ERROR) + .setValue(new Coding().setSystem(CodeSystem.DsfPingError.URL).setCode(testConcept.getCode()) + .setDisplay(testConcept.getDisplay())); + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_POTENTIAL_FIX).setValue(new UrlType(testString)); + + return extension; + } + + private Extension getExtensionMissingFixUrl() + { + Extension extension = new Extension(); + extension.setUrl(ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_ERROR); + + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_ERROR) + .setValue(new Coding().setSystem(CodeSystem.DsfPingError.URL).setCode(testConcept.getCode()) + .setDisplay(testConcept.getDisplay())); + + return extension; + } + + private Extension getExtensionMissingError() + { + Extension extension = new Extension(); + extension.setUrl(ConstantsPing.STRUCTURE_DEFINITION_URL_EXTENSION_ERROR); + + extension.addExtension().setUrl(ConstantsPing.EXTENSION_URL_POTENTIAL_FIX).setValue(new UrlType(testString)); + + return extension; + } +} diff --git a/src/test/java/dev/dsf/library/RandomByteInputStreamTest.java b/src/test/java/dev/dsf/library/RandomByteInputStreamTest.java new file mode 100644 index 00000000..8d923ce6 --- /dev/null +++ b/src/test/java/dev/dsf/library/RandomByteInputStreamTest.java @@ -0,0 +1,170 @@ +package dev.dsf.library; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Ignore; +import org.junit.Test; + +import dev.dsf.bpe.service.RandomByteInputStream; + +public class RandomByteInputStreamTest +{ + @Test + @Ignore + public void ReadLargeLongValueTest() + { + try (RandomByteInputStream inputStream = new RandomByteInputStream(10000000000L)) + { + int out; + long mod = 10; + long count = 0; + while ((out = inputStream.read()) >= 0) + { + count++; + if (count % mod == 0) + { + mod *= 10; + System.out.println(count); + } + } + assertEquals(-1, out); + assertTrue(inputStream.isClosed()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @Test + public void ReadSmallLongValueTest() + { + try (RandomByteInputStream inputStream = new RandomByteInputStream(1000000L)) + { + int out; + long mod = 10; + long count = 0; + while ((out = inputStream.read()) >= 0) + { + count++; + if (count % mod == 0) + { + mod *= 10; + System.out.println(count); + } + } + assertEquals(-1, out); + assertTrue(inputStream.isClosed()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @Test + public void ReadPartIntoArrayTest() + { + try (RandomByteInputStream inputStream = new RandomByteInputStream(1000000L)) + { + byte[] bytes = new byte[10000]; + int out = inputStream.read(bytes); + assertEquals(bytes.length, out); + assertFalse(inputStream.isClosed()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @Test + public void ReadAllIntoArrayTest() + { + try (RandomByteInputStream inputStream = new RandomByteInputStream(1000000L)) + { + byte[] bytes = new byte[1000000]; + int out = inputStream.read(bytes); + assertEquals(bytes.length, out); + assertTrue(inputStream.isClosed()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @Test + public void ReadPartIntoArrayOffsetTest() + { + try (RandomByteInputStream inputStream = new RandomByteInputStream(1000000L)) + { + byte[] bytes = new byte[10000]; + int offset = bytes.length / 2; + int amount = bytes.length / 10; + int out = inputStream.read(bytes, offset, amount); + assertEquals(amount, out); + assertFalse(inputStream.isClosed()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @Test + public void ReadHalfIntoArrayOffsetTest() + { + try (RandomByteInputStream inputStream = new RandomByteInputStream(1000000L)) + { + byte[] bytes = new byte[1000000]; + int offset = bytes.length / 2; + int amount = bytes.length / 2; + int out = inputStream.read(bytes, offset, amount); + assertEquals(amount, out); + assertFalse(inputStream.isClosed()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @Test + public void ReadAllIntoArrayOffsetTest() + { + try (RandomByteInputStream inputStream = new RandomByteInputStream(1000000L)) + { + byte[] bytes = new byte[1000000]; + int offset = 0; + int amount = bytes.length; + int out = inputStream.read(bytes, offset, amount); + assertEquals(amount, out); + assertTrue(inputStream.isClosed()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + @Test + public void ReadAllIntoArrayOffsetAmountTooHighTest() + { + try (RandomByteInputStream inputStream = new RandomByteInputStream(1000000L)) + { + byte[] bytes = new byte[1000000]; + int offset = bytes.length / 2; + int amount = bytes.length; + int out = inputStream.read(bytes, offset, amount); + assertEquals(offset, out); + assertFalse(inputStream.isClosed()); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/resources/fhir/CodeSystem/dsf-ping-1_0.xml b/src/test/resources/fhir/CodeSystem/dsf-ping-1_0.xml new file mode 100644 index 00000000..c2586c9f --- /dev/null +++ b/src/test/resources/fhir/CodeSystem/dsf-ping-1_0.xml @@ -0,0 +1,49 @@ +<CodeSystem xmlns="http://hl7.org/fhir"> + <meta> + <tag> + <system value="http://dsf.dev/fhir/CodeSystem/read-access-tag" /> + <code value="ALL" /> + </tag> + </meta> + <url value="http://dsf.dev/fhir/CodeSystem/ping" /> + <!-- version managed by bpe --> + <version value="1.0" /> + <name value="DSF_Ping" /> + <title value="DSF Ping" /> + <!-- status managed by bpe --> + <status value="unknown" /> + <experimental value="false" /> + <!-- date managed by bpe --> + <date value="#{date}" /> + <publisher value="DSF" /> + <description value="CodeSystem with standard values for the process ping/pong" /> + <caseSensitive value="true" /> + <hierarchyMeaning value="grouped-by" /> + <versionNeeded value="false" /> + <content value="complete" /> + <concept> + <code value="ping-status" /> + <display value="Ping Status" /> + <definition value="Ping status of target organization" /> + </concept> + <concept> + <code value="pong-status" /> + <display value="Pong Status" /> + <definition value="Pong status of target organization" /> + </concept> + <concept> + <code value="endpoint-identifier" /> + <display value="Endpoint Identifier" /> + <definition value="Identifier of endpoint expecting pong (response) message" /> + </concept> + <concept> + <code value="target-endpoints" /> + <display value="Target Endpoints" /> + <definition value="Ping target endpoints, search query resulting in match or include results with Endpoint resources" /> + </concept> + <concept> + <code value="timer-interval" /> + <display value="Timer Interval" /> + <definition value="Interval between two autostarts of the ping process" /> + </concept> +</CodeSystem> \ No newline at end of file diff --git a/src/test/resources/fhir/CodeSystem/dsf-ping-status-1_0.xml b/src/test/resources/fhir/CodeSystem/dsf-ping-status-1_0.xml new file mode 100644 index 00000000..6a1bd2c4 --- /dev/null +++ b/src/test/resources/fhir/CodeSystem/dsf-ping-status-1_0.xml @@ -0,0 +1,49 @@ +<CodeSystem xmlns="http://hl7.org/fhir"> + <meta> + <tag> + <system value="http://dsf.dev/fhir/CodeSystem/read-access-tag" /> + <code value="ALL" /> + </tag> + </meta> + <url value="http://dsf.dev/fhir/CodeSystem/ping-status" /> + <!-- version managed by bpe --> + <version value="1.0" /> + <name value="DSF_Ping_Status" /> + <title value="DSF Ping Status" /> + <!-- status managed by bpe --> + <status value="unknown" /> + <experimental value="false" /> + <!-- date managed by bpe --> + <date value="#{date}" /> + <publisher value="DSF" /> + <description value="CodeSystem with ping/pong status values" /> + <caseSensitive value="true" /> + <hierarchyMeaning value="grouped-by" /> + <versionNeeded value="false" /> + <content value="complete" /> + <concept> + <code value="not-allowed" /> + <display value="Not allowed" /> + <definition value="Not allowed to start pong-process at target organization" /> + </concept> + <concept> + <code value="not-reachable" /> + <display value="Not reachable" /> + <definition value="Ping could not be sent to target organization" /> + </concept> + <concept> + <code value="pong-missing" /> + <display value="Pong missing" /> + <definition value="No pong received from target organization" /> + </concept> + <concept> + <code value="pong-received" /> + <display value="Pong received" /> + <definition value="Pong received from target organization" /> + </concept> + <concept> + <code value="pong-send" /> + <display value="Pong send" /> + <definition value="Pong successfully sent to target organization" /> + </concept> +</CodeSystem> \ No newline at end of file diff --git a/src/test/resources/fhir/ValueSet/dsf-ping-1_0.xml b/src/test/resources/fhir/ValueSet/dsf-ping-1_0.xml new file mode 100644 index 00000000..e52dfd87 --- /dev/null +++ b/src/test/resources/fhir/ValueSet/dsf-ping-1_0.xml @@ -0,0 +1,27 @@ +<ValueSet xmlns="http://hl7.org/fhir"> + <meta> + <tag> + <system value="http://dsf.dev/fhir/CodeSystem/read-access-tag" /> + <code value="ALL" /> + </tag> + </meta> + <url value="http://dsf.dev/fhir/ValueSet/ping" /> + <!-- version managed by bpe --> + <version value="1.0" /> + <name value="DSF_Ping" /> + <title value="DSF Ping" /> + <!-- status managed by bpe --> + <status value="unknown" /> + <experimental value="false" /> + <!-- date managed by bpe --> + <date value="#{date}" /> + <publisher value="DSF" /> + <description value="ValueSet with standard values for the process ping/pong" /> + <immutable value="true" /> + <compose> + <include> + <system value="http://dsf.dev/fhir/CodeSystem/ping" /> + <version value="1.0" /> + </include> + </compose> +</ValueSet> \ No newline at end of file diff --git a/src/test/resources/fhir/ValueSet/dsf-ping-status-1_0.xml b/src/test/resources/fhir/ValueSet/dsf-ping-status-1_0.xml new file mode 100644 index 00000000..afc26d27 --- /dev/null +++ b/src/test/resources/fhir/ValueSet/dsf-ping-status-1_0.xml @@ -0,0 +1,44 @@ +<ValueSet xmlns="http://hl7.org/fhir"> + <meta> + <tag> + <system + value="http://dsf.dev/fhir/CodeSystem/read-access-tag" /> + <code value="ALL" /> + </tag> + </meta> + <url value="http://dsf.dev/fhir/ValueSet/ping-status" /> + <!-- version managed by bpe --> + <version value="1.0" /> + <name value="DSF_Ping_Status" /> + <title value="DSF Ping Status" /> + <!-- status managed by bpe --> + <status value="unknown" /> + <experimental value="false" /> + <!-- date managed by bpe --> + <date value="#{date}" /> + <publisher value="DSF" /> + <description value="ValueSet with ping status values" /> + <immutable value="true" /> + <compose> + <include> + <system value="http://dsf.dev/fhir/CodeSystem/ping-status" /> + <version value="1.0" /> + <concept> + <code value="not-allowed" /> + <display value="Not allowed" /> + </concept> + <concept> + <code value="not-reachable" /> + <display value="Not reachable" /> + </concept> + <concept> + <code value="pong-missing" /> + <display value="Pong missing" /> + </concept> + <concept> + <code value="pong-received" /> + <display value="Pong received" /> + </concept> + </include> + </compose> +</ValueSet> \ No newline at end of file diff --git a/src/test/resources/fhir/ValueSet/dsf-pong-status-1_0.xml b/src/test/resources/fhir/ValueSet/dsf-pong-status-1_0.xml new file mode 100644 index 00000000..d0661cea --- /dev/null +++ b/src/test/resources/fhir/ValueSet/dsf-pong-status-1_0.xml @@ -0,0 +1,39 @@ +<ValueSet xmlns="http://hl7.org/fhir"> + <meta> + <tag> + <system value="http://dsf.dev/fhir/CodeSystem/read-access-tag" /> + <code value="ALL" /> + </tag> + </meta> + <url value="http://dsf.dev/fhir/ValueSet/pong-status" /> + <!-- version managed by bpe --> + <version value="1.0" /> + <name value="DSF_Pong_Status" /> + <title value="DSF Pong Status" /> + <!-- status managed by bpe --> + <status value="unknown" /> + <experimental value="false" /> + <!-- date managed by bpe --> + <date value="#{date}" /> + <publisher value="DSF" /> + <description value="ValueSet with pong status values" /> + <immutable value="true" /> + <compose> + <include> + <system value="http://dsf.dev/fhir/CodeSystem/ping-status" /> + <version value="1.0" /> + <concept> + <code value="not-allowed" /> + <display value="Not allowed" /> + </concept> + <concept> + <code value="not-reachable" /> + <display value="Not reachable" /> + </concept> + <concept> + <code value="pong-send" /> + <display value="Pong send" /> + </concept> + </include> + </compose> +</ValueSet> \ No newline at end of file