diff --git a/.gitignore b/.gitignore index 27bac84b5e..726edc884a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ work /commons/ihe/xds/generated-stubs generated-stubs /commons/audit/.vertx +/local_history.patch diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java index 89cffc9e08..cc5cdb5ed9 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/AuditContext.java @@ -111,8 +111,8 @@ default SerializationStrategy getSerializationStrategy() { default void audit(AuditMessage... messages) { if (isAuditEnabled() && messages != null) { getAuditMessageQueue().audit(this, Stream.of(messages) - .map(getAuditMessagePostProcessor()) - .toArray(AuditMessage[]::new)); + .map(getAuditMessagePostProcessor()) + .toArray(AuditMessage[]::new)); } } @@ -165,6 +165,10 @@ default String getAuditValueIfMissing() { */ boolean isIncludeParticipantsFromResponse(); + String getAuditRepositoryContextPath(); + + String getFhirHttpClient(); + static AuditContext noAudit() { return DefaultAuditContext.NO_AUDIT; } diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java index 79f7801e9b..752720e1c6 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/DefaultAuditContext.java @@ -101,6 +101,14 @@ public class DefaultAuditContext implements AuditContext { @Setter private String auditValueIfMissing = "UNKNOWN"; + @Getter + @Setter + private String auditRepositoryContextPath = ""; + + @Getter + @Setter + private String fhirHttpClient = "apache"; + public String getAuditRepositoryTransport() { return auditTransmissionProtocol.getTransportName(); } @@ -111,7 +119,7 @@ public void setAuditRepositoryHost(String host) { public void setAuditRepositoryTransport(String transport) { setAuditTransmissionProtocol( - AuditTransmissionChannel.fromProtocolName(transport).makeInstance(tlsParameters) + AuditTransmissionChannel.fromProtocolName(transport).makeInstance(tlsParameters) ); } diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionChannel.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionChannel.java index a8a6b87bd1..13f76068b8 100644 --- a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionChannel.java +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionChannel.java @@ -18,33 +18,42 @@ import org.openehealth.ipf.commons.audit.AuditException; import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.providers.NettyTLSSyslogSenderProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.ReactorNettyTLSSyslogSenderProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.RecordingAuditMessageTransmissionProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.TLSSyslogSenderProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.UDPSyslogSenderProvider; +import org.openehealth.ipf.commons.audit.protocol.providers.VertxTLSSyslogSenderProvider; import java.util.Arrays; +import java.util.ServiceLoader; import java.util.stream.Collectors; /** - * Maps AuditTransmissionProtocol names to instances + * Maps AuditTransmissionProtocol names to service providers which should provide a concrete instance of an + * {@link AuditTransmissionProtocol} over a {@link ServiceLoader} mechanism. * * @author Christian Ohr */ public enum AuditTransmissionChannel { - UDP("UDP", UDPSyslogSenderImpl.class), - NIO_UDP("NIO-UDP", UDPSyslogSenderImpl.class), - VERTX_UDP("VERTX-UDP", UDPSyslogSenderImpl.class), - TLS("TLS", TLSSyslogSenderImpl.class), - NIO_TLS("NIO-TLS", NettyTLSSyslogSenderImpl.class), - VERTX_TLS("VERTX-TLS", VertxTLSSyslogSenderImpl.class), - NETTY_TLS("NETTY-TLS", NettyTLSSyslogSenderImpl.class), - REACTOR_NETTY_TLS("REACTOR-NETTY-TLS", ReactorNettyTLSSyslogSenderImpl.class), - RECORDING("RECORDING", RecordingAuditMessageTransmission.class); + UDP("UDP", UDPSyslogSenderProvider.class.getName()), + NIO_UDP("NIO-UDP", UDPSyslogSenderProvider.class.getName()), + VERTX_UDP("VERTX-UDP", UDPSyslogSenderProvider.class.getName()), + TLS("TLS", TLSSyslogSenderProvider.class.getName()), + NIO_TLS("NIO-TLS", NettyTLSSyslogSenderProvider.class.getName()), + VERTX_TLS("VERTX-TLS", VertxTLSSyslogSenderProvider.class.getName()), + NETTY_TLS("NETTY-TLS", NettyTLSSyslogSenderProvider.class.getName()), + REACTOR_NETTY_TLS("REACTOR-NETTY-TLS", ReactorNettyTLSSyslogSenderProvider.class.getName()), + FHIR_REST_TLS("FHIR-REST-TLS", "org.openehealth.ipf.commons.ihe.fhir.audit.protocol.FhirRestTLSAuditRecordSenderProvider"), + RECORDING("RECORDING", RecordingAuditMessageTransmissionProvider.class.getName()); private final String protocolName; - private final Class protocol; + private final String protocolClass; - AuditTransmissionChannel(String protocolName, Class protocol) { + AuditTransmissionChannel(String protocolName, String protocolClass) { this.protocolName = protocolName; - this.protocol = protocol; + this.protocolClass = protocolClass; } public String getProtocolName() { @@ -52,11 +61,13 @@ public String getProtocolName() { } public AuditTransmissionProtocol makeInstance(TlsParameters tlsParameters) { - try { - return protocol.getConstructor(TlsParameters.class).newInstance(tlsParameters); - } catch (Exception e) { - throw new AuditException(e); + ServiceLoader loader = ServiceLoader.load(AuditTransmissionProtocolProvider.class); + for (AuditTransmissionProtocolProvider provider : loader) { + if (protocolClass.equals(provider.getClass().getName())) { + return provider.createAuditTransmissionProtocol(tlsParameters); + } } + throw new AuditException("Could not instantiate AuditTransmissionProtocolProvider for name " + protocolName); } public static AuditTransmissionChannel fromProtocolName(String protocolName) { @@ -66,9 +77,10 @@ public static AuditTransmissionChannel fromProtocolName(String protocolName) { } } throw new IllegalArgumentException("Unknown audit protocol name: " + protocolName + - ". Choose one of: " + - Arrays.stream(AuditTransmissionChannel.values()) - .map(AuditTransmissionChannel::getProtocolName) - .collect(Collectors.joining(","))); + ". Choose one of: " + + Arrays.stream(AuditTransmissionChannel.values()) + .map(AuditTransmissionChannel::getProtocolName) + .collect(Collectors.joining(","))); } + } diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionProtocolProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionProtocolProvider.java new file mode 100644 index 0000000000..ed36133136 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/AuditTransmissionProtocolProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol; + +import org.openehealth.ipf.commons.audit.TlsParameters; + +/** + * A Service Provider interface located and loaded by {@link java.util.ServiceLoader}. + * Implementations should provide an instance of {@link AuditTransmissionProtocol} + * + * @see AuditTransmissionProtocol + * @see AuditTransmissionChannel + * + * @author Boris Stanojevic + */ +public interface AuditTransmissionProtocolProvider { + AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters); +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/NettyTLSSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/NettyTLSSyslogSenderProvider.java new file mode 100644 index 0000000000..30f25515e0 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/NettyTLSSyslogSenderProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.NettyTLSSyslogSenderImpl; + +public class NettyTLSSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new NettyTLSSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/ReactorNettyTLSSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/ReactorNettyTLSSyslogSenderProvider.java new file mode 100644 index 0000000000..a18833afce --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/ReactorNettyTLSSyslogSenderProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.ReactorNettyTLSSyslogSenderImpl; + +public class ReactorNettyTLSSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new ReactorNettyTLSSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/RecordingAuditMessageTransmissionProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/RecordingAuditMessageTransmissionProvider.java new file mode 100644 index 0000000000..e47861808a --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/RecordingAuditMessageTransmissionProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.RecordingAuditMessageTransmission; + +public class RecordingAuditMessageTransmissionProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new RecordingAuditMessageTransmission(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/TLSSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/TLSSyslogSenderProvider.java new file mode 100644 index 0000000000..d2c66d9bd5 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/TLSSyslogSenderProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.TLSSyslogSenderImpl; + +public class TLSSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new TLSSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/UDPSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/UDPSyslogSenderProvider.java new file mode 100644 index 0000000000..fd58e63496 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/UDPSyslogSenderProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.UDPSyslogSenderImpl; + +public class UDPSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new UDPSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxTLSSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxTLSSyslogSenderProvider.java new file mode 100644 index 0000000000..7fc0f74424 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxTLSSyslogSenderProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.VertxTLSSyslogSenderImpl; + +@Deprecated +public class VertxTLSSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new VertxTLSSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxUDPSyslogSenderProvider.java b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxUDPSyslogSenderProvider.java new file mode 100644 index 0000000000..3aa76668c7 --- /dev/null +++ b/commons/audit/src/main/java/org/openehealth/ipf/commons/audit/protocol/providers/VertxUDPSyslogSenderProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.audit.protocol.providers; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.audit.protocol.VertxUDPSyslogSenderImpl; + +@Deprecated +public class VertxUDPSyslogSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new VertxUDPSyslogSenderImpl(tlsParameters); + } +} diff --git a/commons/audit/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider b/commons/audit/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider new file mode 100644 index 0000000000..256537f434 --- /dev/null +++ b/commons/audit/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider @@ -0,0 +1,7 @@ +org.openehealth.ipf.commons.audit.protocol.providers.TLSSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.UDPSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.NettyTLSSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.ReactorNettyTLSSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.RecordingAuditMessageTransmissionProvider +org.openehealth.ipf.commons.audit.protocol.providers.VertxTLSSyslogSenderProvider +org.openehealth.ipf.commons.audit.protocol.providers.VertxUDPSyslogSenderProvider \ No newline at end of file diff --git a/commons/ihe/fhir/core/pom.xml b/commons/ihe/fhir/core/pom.xml index e5566be07d..39e6bb4c72 100644 --- a/commons/ihe/fhir/core/pom.xml +++ b/commons/ihe/fhir/core/pom.xml @@ -84,6 +84,11 @@ hapi-fhir-structures-r4 test + + io.undertow + undertow-servlet + test + diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java index 03e6b25946..e9b1812e98 100644 --- a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/codes/Constants.java @@ -18,7 +18,14 @@ public class Constants { - public static final String IHE_SYSTEM_NAME = "IHE Transactions"; + public static final String IHE_SYSTEM_NAME = "urn:ihe:event-type-code"; public static final String EHS_SYSTEM_NAME = "e-health-suisse"; + public static final String DCM_SYSTEM_NAME = "http://dicom.nema.org/resources/ontology/DCM"; + public static final String SECURITY_SOURCE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/security-source-type"; + public static final String AUDIT_ENTITY_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/audit-entity-type"; + public static final String OBJECT_ROLE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/object-role"; + public static final String AUDIT_LIFECYCLE_SYSTEM_NAME = "http://terminology.hl7.org/CodeSystem/dicom-audit-lifecycle"; + + } diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordSenderImpl.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordSenderImpl.java new file mode 100644 index 0000000000..8fe71123e3 --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordSenderImpl.java @@ -0,0 +1,141 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.api.IGenericClient; +import ca.uhn.fhir.rest.client.impl.RestfulClientFactory; +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.AuditMetadataProvider; +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionChannel; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareAbstractRestfulClientFactory; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareApacheRestfulClientFactory; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareMethanolRestfulClientFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Objects; + +import static ca.uhn.fhir.context.FhirContext.forR4; + +/** + * FHIR REST client for sending FHIR Audit Events to an Audit Record Repository. + * + * @author Boris Stanojevic + * @since 4.8 + */ +public class FhirRestTLSAuditRecordSenderImpl implements AuditTransmissionProtocol { + + private IGenericClient client; + private static final String BASE_URL_FORMAT = "https://%s:%s/%s"; + private FhirContext context; + private TlsParameters tlsParameters; + private static final Logger LOG = LoggerFactory.getLogger(FhirRestTLSAuditRecordSenderImpl.class); + + public FhirRestTLSAuditRecordSenderImpl(final FhirContext context, String baseUrl) { + this.context = Objects.requireNonNull(context, "FhirContext must not be null"); + createClient(baseUrl); + } + + public FhirRestTLSAuditRecordSenderImpl(RestfulClientFactory restfulClientFactory, String baseUrl) { + this.context = Objects + .requireNonNull(restfulClientFactory, "RestfulClientFactory must not be null") + .getFhirContext(); + createClient(baseUrl); + } + + public FhirRestTLSAuditRecordSenderImpl(TlsParameters tlsParameters) { + this.context = forR4(); + this.tlsParameters = tlsParameters; + } + + @Override + public void send(AuditContext auditContext, + AuditMetadataProvider auditMetadataProvider, + String auditEvent) throws Exception { + if (client == null) { + new TlsParametersAwareRestfulClientFactory( + this.context, + this.tlsParameters, + auditContext.getFhirHttpClient()); + String baseUrl = String.format(BASE_URL_FORMAT, + auditContext.getAuditRepositoryHostName(), + auditContext.getAuditRepositoryPort(), + auditContext.getAuditRepositoryContextPath()); + createClient(baseUrl); + } + MethodOutcome outcome = client + .create() + .resource(auditEvent) + .execute(); + + LOG.debug("Audit Repository Response: " + outcome.getResponseStatusCode()); + } + + private synchronized void createClient(String baseUrl) { + if (client == null) { + client = context.getRestfulClientFactory().newGenericClient(baseUrl); + } + } + + @Override + public void shutdown() { + + } + + @Override + public String getTransportName() { + return AuditTransmissionChannel.FHIR_REST_TLS.getProtocolName(); + } + + private static final class TlsParametersAwareRestfulClientFactory { + + private final FhirContext fhirContext; + private final RestfulClientFactory restfulClientFactory; + private static final String DEFAULT_HTTP_CLIENT = "apache"; + + public TlsParametersAwareRestfulClientFactory(FhirContext fhirContext, TlsParameters tlsParameters) { + this.fhirContext = fhirContext; + this.restfulClientFactory = createRestfulFactory(tlsParameters, DEFAULT_HTTP_CLIENT); + } + + public TlsParametersAwareRestfulClientFactory(FhirContext fhirContext, + TlsParameters tlsParameters, + String httpClient) { + this.fhirContext = fhirContext; + this.restfulClientFactory = createRestfulFactory(tlsParameters, httpClient); + } + + private RestfulClientFactory createRestfulFactory(TlsParameters tlsParameters, String httpClient) { + SslAwareAbstractRestfulClientFactory factory = "methanol".equalsIgnoreCase(httpClient)? + new SslAwareMethanolRestfulClientFactory(fhirContext) : + new SslAwareApacheRestfulClientFactory(fhirContext); + + factory.initializeSecurityInformation(true, + tlsParameters.getSSLContext(false), null, "", ""); + fhirContext.setRestfulClientFactory(factory); + return factory; + } + + public RestfulClientFactory getRestfulClientFactory() { + return this.restfulClientFactory; + } + } +} diff --git a/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordSenderProvider.java b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordSenderProvider.java new file mode 100644 index 0000000000..a10bb5ac11 --- /dev/null +++ b/commons/ihe/fhir/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestTLSAuditRecordSenderProvider.java @@ -0,0 +1,13 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocol; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider; + +public class FhirRestTLSAuditRecordSenderProvider implements AuditTransmissionProtocolProvider { + + @Override + public AuditTransmissionProtocol createAuditTransmissionProtocol(TlsParameters tlsParameters) { + return new FhirRestTLSAuditRecordSenderImpl(tlsParameters); + } +} diff --git a/commons/ihe/fhir/core/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider b/commons/ihe/fhir/core/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider new file mode 100644 index 0000000000..331c8502ba --- /dev/null +++ b/commons/ihe/fhir/core/src/main/resources/META-INF/services/org.openehealth.ipf.commons.audit.protocol.AuditTransmissionProtocolProvider @@ -0,0 +1 @@ +org.openehealth.ipf.commons.ihe.fhir.audit.protocol.FhirRestTLSAuditRecordSenderProvider \ No newline at end of file diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java new file mode 100644 index 0000000000..21b7d46ef4 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/AbstractFhirRestTLSSenderIntegrationTest.java @@ -0,0 +1,115 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.audit.CustomTlsParameters; +import org.openehealth.ipf.commons.audit.DefaultAuditContext; +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator; +import org.openehealth.ipf.commons.audit.event.ApplicationActivityBuilder; +import org.openehealth.ipf.commons.audit.protocol.AuditTransmissionChannel; +import org.openehealth.ipf.commons.audit.utils.AuditUtils; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.file.Paths; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(FhirAuditRepository.class) +public abstract class AbstractFhirRestTLSSenderIntegrationTest { + + protected DefaultAuditContext auditContext; + + private static final Logger LOG = LoggerFactory.getLogger(AbstractFhirRestTLSSenderIntegrationTest.class); + + @BeforeEach + public void setup() { + this.auditContext = new DefaultAuditContext(); + auditContext.setAuditRepositoryPort(FhirAuditRepository.getServerHttpsPort()); + auditContext.setAuditRepositoryHost("localhost"); + auditContext.setAuditEnabled(true); + auditContext.setSerializationStrategy((auditMessage, writer, pretty) -> writer.write("")); + var defaultTls = setupDefaultTlsParameter(); + auditContext.setTlsParameters(defaultTls); + auditContext.setAuditRepositoryTransport(AuditTransmissionChannel.FHIR_REST_TLS.getProtocolName()); + } + + TlsParameters setupDefaultTlsParameter() { + try { + var tlsParameters = new CustomTlsParameters(); + tlsParameters.setKeyStoreFile(Paths.get(AbstractFhirRestTLSSenderIntegrationTest.class.getResource("/security/client.p12").toURI()).toString()); + tlsParameters.setKeyStorePassword("init"); + tlsParameters.setTrustStoreFile(Paths.get(AbstractFhirRestTLSSenderIntegrationTest.class.getResource("/security/ca.keystore").toURI()).toString()); + tlsParameters.setTrustStorePassword("initinit"); + tlsParameters.setEnabledProtocols("TLSv1.2,TLSv1.3"); + return tlsParameters; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @AfterEach + public void tearDown() { + LOG.info("FhirAuditRepository size: " + FhirAuditRepository.getAuditEvents().size() + ". Cleanup...."); + FhirAuditRepository.getAuditEvents().clear(); + LOG.info("FhirAuditRepository size: " + FhirAuditRepository.getAuditEvents().size()); + } + + @Test + public void testTwoWayTLSFlooding() throws Exception { + var count = 500; + var threads = 2; + var executor = Executors.newFixedThreadPool(threads); + + IntStream.range(0, count).forEach(i -> executor.execute(() -> sendAudit(Integer.toString(i)))); + CountDownLatch latch = new CountDownLatch(1); + Executors.newSingleThreadExecutor().execute(new ExpectationRunnable(latch, count)); + assertTrue(latch.await(10, TimeUnit.SECONDS)); + } + + void sendAudit(String userName) { + LOG.debug("Sending audit record"); + auditContext.audit( + new ApplicationActivityBuilder.ApplicationStop(EventOutcomeIndicator.Success) + .setAuditSource(auditContext) + .setApplicationParticipant( + userName, + null, + null, + AuditUtils.getLocalHostName()) + .addApplicationStarterParticipant(System.getProperty("user.name")) + .getMessages()); + } + + static class ExpectationRunnable implements Runnable { + + private final CountDownLatch latch; + + private final int expectedMessagesCount; + + public ExpectationRunnable(CountDownLatch latch, int expectedMessagesCount) { + this.latch = latch; + this.expectedMessagesCount = expectedMessagesCount; + } + + @Override + public void run() { + while (FhirAuditRepository.getAuditEvents().size() < expectedMessagesCount) { + try { + Thread.sleep(1000); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } + latch.countDown(); + } + } +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestApacheTLSSenderIntegrationTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestApacheTLSSenderIntegrationTest.java new file mode 100644 index 0000000000..3e0de37183 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestApacheTLSSenderIntegrationTest.java @@ -0,0 +1,12 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import org.junit.jupiter.api.BeforeEach; + +public class FhirRestApacheTLSSenderIntegrationTest extends AbstractFhirRestTLSSenderIntegrationTest { + + @BeforeEach + public void setup() { + super.setup(); + auditContext.setFhirHttpClient("apache"); + } +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestMethanolTLSSenderIntegrationTest.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestMethanolTLSSenderIntegrationTest.java new file mode 100644 index 0000000000..f308e4021d --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/protocol/FhirRestMethanolTLSSenderIntegrationTest.java @@ -0,0 +1,12 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.protocol; + +import org.junit.jupiter.api.BeforeEach; + +public class FhirRestMethanolTLSSenderIntegrationTest extends AbstractFhirRestTLSSenderIntegrationTest { + + @BeforeEach + public void setup() { + super.setup(); + auditContext.setFhirHttpClient("methanol"); + } +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/queue/FhirDelegateMockedMessageQueue.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/queue/FhirDelegateMockedMessageQueue.java new file mode 100644 index 0000000000..4e940723b8 --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/audit/queue/FhirDelegateMockedMessageQueue.java @@ -0,0 +1,43 @@ +package org.openehealth.ipf.commons.ihe.fhir.audit.queue; + +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.model.AuditMessage; +import org.openehealth.ipf.commons.audit.queue.AbstractMockedAuditMessageQueue; +import org.openehealth.ipf.commons.audit.queue.AuditMessageQueue; +import org.openehealth.ipf.commons.audit.queue.SynchronousAuditMessageQueue; + +import java.util.List; + +public class FhirDelegateMockedMessageQueue implements AbstractMockedAuditMessageQueue { + + private final AuditMessageQueue delegate; + + public FhirDelegateMockedMessageQueue() { + this.delegate = new SynchronousAuditMessageQueue(); + } + + @Override + public void audit(AuditContext auditContext, AuditMessage... auditMessages) { + delegate.audit(auditContext, auditMessages); + } + + @Override + public void flush() { + delegate.flush(); + } + + @Override + public void shutdown() { + delegate.shutdown(); + } + + @Override + public List getMessages() { + return null; + } + + @Override + public void clear() { + + } +} diff --git a/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java new file mode 100644 index 0000000000..a8351b909a --- /dev/null +++ b/commons/ihe/fhir/core/src/test/java/org/openehealth/ipf/commons/ihe/fhir/extension/FhirAuditRepository.java @@ -0,0 +1,192 @@ +package org.openehealth.ipf.commons.ihe.fhir.extension; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.RestfulServer; +import io.undertow.Handlers; +import io.undertow.Undertow; +import io.undertow.UndertowOptions; +import io.undertow.server.HttpHandler; +import io.undertow.server.handlers.PathHandler; +import io.undertow.servlet.api.DeploymentInfo; +import io.undertow.servlet.api.DeploymentManager; +import io.undertow.servlet.api.InstanceFactory; +import io.undertow.servlet.api.InstanceHandle; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.IdType; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.openehealth.ipf.commons.audit.CustomTlsParameters; +import org.openehealth.ipf.commons.audit.TlsParameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; + +import java.net.ServerSocket; +import java.net.URI; +import java.nio.file.Paths; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CopyOnWriteArrayList; + +import static io.undertow.servlet.Servlets.defaultContainer; +import static io.undertow.servlet.Servlets.deployment; +import static io.undertow.servlet.Servlets.servlet; +import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL; + +public class FhirAuditRepository implements BeforeAllCallback, BeforeEachCallback { + private static Undertow server; + private ExtensionContext extensionContext; + private static FhirAuditServer fhirAuditServer; + private static final String STORE_KEY = "undertow"; + private static int httpsPort ; + static final String SERVER_KEY_STORE; + static final String SERVER_KEY_STORE_PASS = "init"; + static final String TRUST_STORE; + static final String TRUST_STORE_PASS = "initinit"; + + private static final Logger LOGGER = LoggerFactory.getLogger(FhirAuditRepository.class); + + static { + try { + URI s = new ClassPathResource("/security/server.p12").getURI(); + URI t = new ClassPathResource("/security/ca.keystore").getURI(); + SERVER_KEY_STORE = Paths.get(s).toAbsolutePath().toString(); + TRUST_STORE = Paths.get(t).toAbsolutePath().toString(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + TlsParameters setupDefaultTlsParameter() { + var tlsParameters = new CustomTlsParameters(); + tlsParameters.setKeyStoreFile(SERVER_KEY_STORE); + tlsParameters.setKeyStorePassword(SERVER_KEY_STORE_PASS); + tlsParameters.setTrustStoreFile(TRUST_STORE); + tlsParameters.setTrustStorePassword(TRUST_STORE_PASS); + tlsParameters.setEnabledProtocols("TLSv1.2,TLSv1.3"); + return tlsParameters; + } + + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + this.extensionContext = extensionContext; + if (hasStartedUndertow()) return; + + httpsPort = freePort(); + registerShutdownHook(); + } + + private int freePort() throws Exception { + try (ServerSocket socket = new ServerSocket(0)) { + socket.setReuseAddress(true); + return socket.getLocalPort(); + } + } + + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + if (server == null) { + fhirAuditServer = new FhirAuditServer(); + DeploymentInfo servletBuilder = deployment() + .setClassLoader(FhirAuditRepository.class.getClassLoader()) + .setContextPath("/") + .setDeploymentName("FHIR-Deployment") + .addServlets( + servlet("FhirAuditServer", FhirAuditServer.class, new FhirServletInitiator(fhirAuditServer)) + .addMapping("/*")); + + DeploymentManager manager = defaultContainer().addDeployment(servletBuilder); + manager.deploy(); + + HttpHandler servletHandler = manager.start(); + PathHandler path = Handlers + .path(Handlers.redirect("/")) + .addPrefixPath("/", servletHandler); + server = Undertow.builder() + .setServerOption(UndertowOptions.ENABLE_HTTP2, true) + .addHttpsListener( + httpsPort,"localhost", setupDefaultTlsParameter().getSSLContext(true)) + .setHandler(path) + .build(); + server.start(); + } + } + + private void registerShutdownHook() { + ExtensionContext.Store.CloseableResource closeableResource = () -> { + LOGGER.info("stopping undertow server..."); + if (server != null) server.stop(); + LOGGER.info("successfully stopped undertow server"); + }; + extensionContext.getRoot().getStore(GLOBAL).put(STORE_KEY, closeableResource); + } + + public static int getServerHttpsPort(){ + return httpsPort; + } + public static List getAuditEvents() { + return fhirAuditServer.getAuditEvents(); + } + + private boolean hasStartedUndertow(){ + return extensionContext.getRoot().getStore(GLOBAL).get(STORE_KEY) != null; + } + + static class FhirAuditServer extends RestfulServer implements IResourceProvider { + + private final List auditEvents = new CopyOnWriteArrayList<>(); + + public FhirAuditServer() { + setFhirContext(FhirContext.forR4()); + setResourceProviders(this); + } + + public List getAuditEvents() { + return auditEvents; + } + + @Create() + public MethodOutcome create(@ResourceParam AuditEvent auditEvent) { + auditEvents.add(auditEvent); + return new MethodOutcome( + new IdType(ResourceType.AuditEvent.name(), + UUID.randomUUID().toString()), true); + } + + @Override + public Class getResourceType() { + return AuditEvent.class; + } + } + + static class FhirServletInitiator implements InstanceFactory { + + private final FhirAuditServer fhirAuditServer; + + public FhirServletInitiator(FhirAuditServer fhirAuditServer) { + this.fhirAuditServer = fhirAuditServer; + } + + @Override + public InstanceHandle createInstance() throws InstantiationException { + return new InstanceHandle<>() { + @Override + public FhirAuditServer getInstance() { + return fhirAuditServer; + } + + @Override + public void release() { + + } + }; + } + } +} \ No newline at end of file diff --git a/commons/ihe/fhir/core/src/test/resources/security/ca.keystore b/commons/ihe/fhir/core/src/test/resources/security/ca.keystore new file mode 100644 index 0000000000..4b334d2e29 Binary files /dev/null and b/commons/ihe/fhir/core/src/test/resources/security/ca.keystore differ diff --git a/commons/ihe/fhir/core/src/test/resources/security/client.p12 b/commons/ihe/fhir/core/src/test/resources/security/client.p12 new file mode 100644 index 0000000000..d10dc9b113 Binary files /dev/null and b/commons/ihe/fhir/core/src/test/resources/security/client.p12 differ diff --git a/commons/ihe/fhir/core/src/test/resources/security/server.p12 b/commons/ihe/fhir/core/src/test/resources/security/server.p12 new file mode 100644 index 0000000000..47ba451774 Binary files /dev/null and b/commons/ihe/fhir/core/src/test/resources/security/server.p12 differ diff --git a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java index 96ef5d7887..b4b8ae4dfa 100644 --- a/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java +++ b/commons/ihe/fhir/r4/core/src/main/java/org/openehealth/ipf/commons/ihe/fhir/support/audit/marshal/AbstractFhirAuditSerializationStrategy.java @@ -22,14 +22,12 @@ import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.r4.model.*; import org.openehealth.ipf.commons.audit.AuditException; -import org.openehealth.ipf.commons.audit.codes.EventActionCode; -import org.openehealth.ipf.commons.audit.codes.EventOutcomeIndicator; -import org.openehealth.ipf.commons.audit.codes.NetworkAccessPointTypeCode; -import org.openehealth.ipf.commons.audit.codes.ParticipantObjectDataLifeCycle; -import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCode; -import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCodeRole; +import org.openehealth.ipf.commons.audit.codes.*; import org.openehealth.ipf.commons.audit.marshal.SerializationStrategy; -import org.openehealth.ipf.commons.audit.model.*; +import org.openehealth.ipf.commons.audit.model.ActiveParticipantType; +import org.openehealth.ipf.commons.audit.model.AuditMessage; +import org.openehealth.ipf.commons.audit.model.AuditSourceIdentificationType; +import org.openehealth.ipf.commons.audit.model.ParticipantObjectIdentificationType; import org.openehealth.ipf.commons.audit.types.CodedValueType; import java.io.IOException; @@ -37,6 +35,8 @@ import java.sql.Date; import java.util.function.Function; +import static org.openehealth.ipf.commons.ihe.fhir.audit.codes.Constants.*; + /** * @author Christian Ohr * @since 3.6 @@ -62,10 +62,10 @@ public void marshal(AuditMessage auditMessage, Writer writer, boolean pretty) th protected abstract IParser getParser(FhirContext fhirContext); - protected AuditEvent translate(AuditMessage auditMessage) { + public AuditEvent translate(AuditMessage auditMessage) { var eit = auditMessage.getEventIdentification(); var auditEvent = new AuditEvent() - .setType(codedValueTypeToCoding(eit.getEventID())) + .setType(codedValueTypeToCoding(eit.getEventID(), DCM_SYSTEM_NAME)) .setAction(getAuditEventAction(eit.getEventActionCode())) .setRecorded(Date.from(eit.getEventDateTime())) .setOutcome(getAuditEventOutcome(eit.getEventOutcomeIndicator())) @@ -87,21 +87,22 @@ protected AuditEvent translate(AuditMessage auditMessage) { protected AuditEvent.AuditEventEntityComponent participantObjectIdentificationToEntity(ParticipantObjectIdentificationType poit) { var entity = new AuditEvent.AuditEventEntityComponent() - .setWhat(new Reference().setIdentifier(new Identifier().setValue(poit.getParticipantObjectID()))) // poit.getParticipantObjectIDTypeCode())) not used here - .setType(codeToCoding("http://hl7.org/fhir/audit-entity-type", poit.getParticipantObjectTypeCode(), ParticipantObjectTypeCode::getValue)) - .setRole(codeToCoding("http://hl7.org/fhir/object-role", poit.getParticipantObjectTypeCodeRole(), ParticipantObjectTypeCodeRole::getValue)) - .setLifecycle(codeToCoding("http://hl7.org/fhir/dicom-audit-lifecycle", poit.getParticipantObjectDataLifeCycle(), ParticipantObjectDataLifeCycle::getValue)) + .setType(codeToCoding(AUDIT_ENTITY_SYSTEM_NAME, poit.getParticipantObjectTypeCode(), ParticipantObjectTypeCode::getValue)) + .setRole(codeToCoding(OBJECT_ROLE_SYSTEM_NAME, poit.getParticipantObjectTypeCodeRole(), ParticipantObjectTypeCodeRole::getValue)) + .setLifecycle(codeToCoding(AUDIT_LIFECYCLE_SYSTEM_NAME, poit.getParticipantObjectDataLifeCycle(), ParticipantObjectDataLifeCycle::getValue)) .addSecurityLabel(codeToCoding(null, poit.getParticipantObjectSensitivity(), Function.identity())) .setName(poit.getParticipantObjectName()) - // poit.getParticipantObjectDescription) not mappable here + // .setDescription(poit.getParticipantObjectDescriptions().isEmpty() ? null : poit.getParticipantObjectDescriptions().get(0).toString()) .setQuery(poit.getParticipantObjectQuery()); poit.getParticipantObjectDetails().forEach(tvp -> entity.addDetail(new AuditEvent.AuditEventEntityDetailComponent() .setType(tvp.getType()) .setValue(new Base64BinaryType(tvp.getValue())))); - + if (poit.getParticipantObjectTypeCodeRole() == ParticipantObjectTypeCodeRole.Patient) { + entity.setWhat(new Reference(poit.getParticipantObjectID())); + } return entity; } @@ -109,15 +110,16 @@ protected AuditEvent.AuditEventEntityComponent participantObjectIdentificationTo protected AuditEvent.AuditEventSourceComponent auditSourceIdentificationToEventSource(AuditSourceIdentificationType asit) { var source = new AuditEvent.AuditEventSourceComponent() .setSite(asit.getAuditEnterpriseSiteID()) - .setObserver(new Reference().setIdentifier(new Identifier().setValue(asit.getAuditSourceID()))); + .setObserver(new Reference().setDisplay(asit.getAuditSourceID())); asit.getAuditSourceType().forEach(ast -> - source.addType(codedValueTypeToCoding(ast))); + source.addType(codedValueTypeToCoding(ast, SECURITY_SOURCE_SYSTEM_NAME))); return source; } protected AuditEvent.AuditEventAgentComponent activeParticipantToAgent(ActiveParticipantType ap) { - var agent = new AuditEvent.AuditEventAgentComponent() - .setWho(new Reference().setIdentifier(new Identifier().setValue(ap.getUserID()))) + return new AuditEvent.AuditEventAgentComponent() + .setType(codedValueTypeToCodeableConcept(ap.getRoleIDCodes().get(0), DCM_SYSTEM_NAME)) + .setWho(new Reference().setDisplay(ap.getUserID())) .setAltId(ap.getAlternativeUserID()) .setName(ap.getUserName()) .setRequestor(ap.isUserIsRequestor()) @@ -125,9 +127,6 @@ protected AuditEvent.AuditEventAgentComponent activeParticipantToAgent(ActivePar .setNetwork(new AuditEvent.AuditEventAgentNetworkComponent() .setAddress(ap.getNetworkAccessPointID()) .setType(auditEventNetworkType(ap.getNetworkAccessPointTypeCode()))); - ap.getRoleIDCodes().forEach(roleID -> - agent.addPolicy(roleID.getCode())); - return agent; } protected AuditEvent.AuditEventAgentNetworkType auditEventNetworkType(NetworkAccessPointTypeCode naptc) { @@ -168,15 +167,27 @@ protected Coding codeToCoding(String codeSystem, T code, Function v protected Coding codedValueTypeToCoding(CodedValueType cvt) { return cvt != null ? - new Coding(cvt.getCodeSystemName(), - cvt.getCode(), - cvt.getOriginalText()) : - null; + codedValueTypeToCoding(cvt, cvt.getCodeSystemName()) : + null; + } + + protected Coding codedValueTypeToCoding(CodedValueType cvt, String codeSystem) { + return cvt != null ? + new Coding(codeSystem, + cvt.getCode(), + cvt.getOriginalText()) : + null; } protected CodeableConcept codedValueTypeToCodeableConcept(CodedValueType cvt) { return cvt != null ? - new CodeableConcept().addCoding(codedValueTypeToCoding(cvt)) : - null; + codedValueTypeToCodeableConcept(cvt, cvt.getCodeSystemName()) : + null; + } + + protected CodeableConcept codedValueTypeToCodeableConcept(CodedValueType cvt, String codeSystem) { + return cvt != null ? + new CodeableConcept().addCoding(codedValueTypeToCoding(cvt, codeSystem)) : + null; } } diff --git a/commons/ihe/fhir/r4/pixpdq/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/iti78/PdqmRequestToPdqQueryTranslator.groovy b/commons/ihe/fhir/r4/pixpdq/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/iti78/PdqmRequestToPdqQueryTranslator.groovy index 1c24de79d7..9590168ac3 100644 --- a/commons/ihe/fhir/r4/pixpdq/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/iti78/PdqmRequestToPdqQueryTranslator.groovy +++ b/commons/ihe/fhir/r4/pixpdq/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/iti78/PdqmRequestToPdqQueryTranslator.groovy @@ -179,6 +179,7 @@ class PdqmRequestToPdqQueryTranslator implements FhirTranslator { '@PID.3.4.3': searchIdentifier.map({ it.oid ? 'ISO' : null }).orElse(null), '@PID.5.1' : firstOrNull(searchStringList(searchParameters.family, false)), '@PID.5.2' : firstOrNull(searchStringList(searchParameters.given, false)), + '@PID.6' : searchString(searchParameters.mothersMaidenName, true), '@PID.7' : convertBirthDate(searchParameters.birthDate), '@PID.8' : genderString, '@PID.11.1' : searchString(searchParameters.address, true), @@ -200,7 +201,7 @@ class PdqmRequestToPdqQueryTranslator implements FhirTranslator { } protected String convertBirthDate(DateAndListParam birthDateParam) { - Date birthDate = firstOrNull(searchDateList(birthDateParam)) + var birthDate = firstOrNull(searchDateList(birthDateParam)) return birthDate ? FastDateFormat.getInstance('yyyyMMdd').format(birthDate) : null } @@ -218,7 +219,7 @@ class PdqmRequestToPdqQueryTranslator implements FhirTranslator { param?.valuesAsQueryTokens?.collect { searchString(it.valuesAsQueryTokens.find(), forceExactSearch) } } - protected List searchDateList(DateAndListParam param) { + protected List searchDateList(DateAndListParam param) { param?.valuesAsQueryTokens?.collect { searchDate(it.valuesAsQueryTokens.find()) } } diff --git a/platform-camel/ihe/fhir/r4/mhd/pom.xml b/platform-camel/ihe/fhir/r4/mhd/pom.xml index a37ff1e89f..f0e8c07e4f 100644 --- a/platform-camel/ihe/fhir/r4/mhd/pom.xml +++ b/platform-camel/ihe/fhir/r4/mhd/pom.xml @@ -43,6 +43,13 @@ test-jar test + + org.openehealth.ipf.commons + ipf-commons-ihe-fhir-core + ${project.version} + test-jar + test + org.apache.camel camel-http @@ -118,6 +125,11 @@ methanol test + + io.undertow + undertow-servlet + test + @@ -133,6 +145,35 @@ + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack + process-resources + + unpack + + + + + org.openehealth.ipf.commons + ipf-commons-ihe-fhir-core + ${project.version} + jar + tests + false + ${project.build.testOutputDirectory} + **/security/*.p12,**/security/*.keystore + + + false + true + + + + diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java new file mode 100644 index 0000000000..2866191db6 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti105/TestIti105WithBalpAudit.java @@ -0,0 +1,142 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti105; + +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import org.hl7.fhir.r4.model.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.IpfFhirServlet; +import org.openehealth.ipf.commons.ihe.fhir.SslAwareMethanolRestfulClientFactory; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; +import org.openehealth.ipf.commons.ihe.fhir.mhd.model.SimplifiedPublishDocumentReference; +import org.openehealth.ipf.platform.camel.ihe.fhir.test.FhirTestContainer; + +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Collections; +import java.util.Date; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.openehealth.ipf.commons.ihe.fhir.Constants.URN_IETF_RFC_3986; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti105WithBalpAudit extends FhirTestContainer { + + private static final String CONTEXT_DESCRIPTOR = "iti-105-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + public static void startServer(String contextDescriptor) { + var servlet = new IpfFhirServlet(FhirVersionEnum.R4); + startServer(servlet, contextDescriptor, false, DEMO_APP_PORT, "FhirServlet"); + + var loggingInterceptor = new LoggingInterceptor(); + loggingInterceptor.setLogRequestSummary(false); + loggingInterceptor.setLogRequestBody(true); + loggingInterceptor.setLogResponseBody(true); + startClient(String.format("http://localhost:%d/", DEMO_APP_PORT), fhirContext -> { + var clientFactory = new SslAwareMethanolRestfulClientFactory(fhirContext); + clientFactory.setAsync(true); + fhirContext.setRestfulClientFactory(clientFactory); + }).registerInterceptor(loggingInterceptor); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testSendRemoteSimplifiedPublish() { + var result = client.create() + .resource(documentReference()) + .encodedXml() + .execute(); + assertNotNull(result); + assertTrue(result.getCreated()); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(1, auditEvents.size()); + var auditEvent = auditEvents.get(0); + + assertEquals("110107", auditEvent.getType().getCode()); + assertEquals("ITI-105", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("C", auditEvent.getAction().toCode()); + assertEquals("Import", auditEvent.getType().getDisplay()); + assertEquals("0", auditEvent.getOutcome().toCode()); + } + + @Test + public void testSendOverDirectEndpointSimplifiedPublish() { + producerTemplate.requestBody("direct:input", documentReference(), MethodOutcome.class); + assertEquals(2, FhirAuditRepository.getAuditEvents().size()); + } + + private static DocumentReference documentReference() { + var documentContent = "Hello IHE World".getBytes(); + var practitioner = new Practitioner(); + practitioner.setId("987"); + practitioner.addName(new HumanName() + .setFamily("Soo") + .setGiven(Collections.singletonList(new StringType("Yun")))); + var patient = new Patient(); + patient.setId("123"); + patient.getName().add(new HumanName() + .setFamily("Foo") + .setGiven(Collections.singletonList(new StringType("Barbara")))); + var timestamp = Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)); + var reference = new SimplifiedPublishDocumentReference(); + reference.getMeta().setLastUpdated(timestamp); + + reference + .setUniqueIdIdentifier(URN_IETF_RFC_3986, "urn:oid:129.6.58.92.88336") + .setContent("text/plain", documentContent) + .addIdentifier(new Identifier() + .setSystem(URN_IETF_RFC_3986) + .setValue("urn:oid:129.6.58.92.88336")) + .setDate(timestamp) // creation of document reference resource + .setDescription("Physical") + .setSubject(new Reference(patient)) + .addAuthor(new Reference(practitioner)) + .setStatus(Enumerations.DocumentReferenceStatus.CURRENT); + + reference.getText().setStatus(Narrative.NarrativeStatus.EMPTY); + reference.getText().setDivAsString("
empty
"); + reference.getType().addCoding() + .setSystem("http://ihe.net/connectathon/classCodes") + .setCode("History and Physical") + .setDisplay("History and Physical"); + reference.getContentFirstRep() + .setFormat(new Coding("urn:oid:1.3.6.1.4.1.19376.1.2.3", "urn:ihe:pcc:handp:2008", null)); + return reference; + } + +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java new file mode 100644 index 0000000000..3af3fc4dc4 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti65/v421/TestIti65WithBalpAudit.java @@ -0,0 +1,89 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti65.v421; + +import org.hl7.fhir.r4.model.AuditEvent; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti65WithBalpAudit extends AbstractTestIti65 { + + private static final String CONTEXT_DESCRIPTOR = "iti-65-balp.xml"; + + @BeforeAll + public static void setUpClass() { + setup(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testSendManualMhd() throws Exception { + sendManually(provideAndRegister()); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(1, auditEvents.size()); + AuditEvent auditEvent = auditEvents.get(0); + assertEquals("110107", auditEvent.getType().getCode()); + assertEquals("Import", auditEvent.getType().getDisplay()); + assertEquals("ITI-65", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("C", auditEvent.getAction().toCode()); + assertEquals("0", auditEvent.getOutcome().toCode()); + Optional sourceRole = findRoleAgentWithCode(auditEvent, "110153"); + assertTrue(sourceRole.isPresent()); + assertTrue(sourceRole.get().getRequestor()); + Optional destinationRole = findRoleAgentWithCode(auditEvent, "110152"); + assertTrue(destinationRole.isPresent()); + assertFalse(destinationRole.get().getRequestor()); + assertEquals(1, auditEvent.getEntity().stream() + .filter(event -> event.getType().getCode().equals("2") && event.getRole().getCode().equals("20")) + .count()); + } + + @Test + public void testSendEndpointMhd() throws Exception { + var bundle = provideAndRegister(); + sendViaProducer(bundle); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(2, auditEvents.size()); + } + + private Optional findRoleAgentWithCode(AuditEvent auditEvent, String code) { + return auditEvent.getAgent().stream() + .filter(p -> p.getType().getCodingFirstRep().getCode().equals(code)) + .findFirst(); + } +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java new file mode 100644 index 0000000000..4cf4e5abf7 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti66/v421/TestIti66WithBalpAudit.java @@ -0,0 +1,102 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti66.v421; + +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.ListResource; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import java.util.Objects; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti66WithBalpAudit extends AbstractTestIti66 { + + private static final String CONTEXT_DESCRIPTOR = "v421/iti-66-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testSendManualIti66() { + var result = sendManually(listPatientIdentifierParameter(), statusParameter()); + + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (ListResource) result.getEntry().get(0).getResource(); + assertEquals("9bc72458-49b0-11e6-8a1c-3c1620524153", p.getIdElement().getIdPart()); + + assertEquals(1, FhirAuditRepository.getAuditEvents().size()); + AuditEvent auditEvent = FhirAuditRepository.getAuditEvents().get(0); + assertEquals("110112", auditEvent.getType().getCode()); + assertEquals("Query", auditEvent.getType().getDisplay()); + assertEquals("ITI-66", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("E", auditEvent.getAction().toCode()); + assertEquals("0", auditEvent.getOutcome().toCode()); + Optional sourceRole = findRoleAgentWithCode(auditEvent, "110153"); + assertTrue(sourceRole.isPresent()); + assertTrue(sourceRole.get().getRequestor()); + Optional destinationRole = findRoleAgentWithCode(auditEvent, "110152"); + assertTrue(destinationRole.isPresent()); + assertFalse(destinationRole.get().getRequestor()); + assertEquals(1, auditEvent.getEntity().stream() + .filter(event -> event.getType().getCode().equals("2") && event.getRole().getCode().equals("24")) + .map(AuditEvent.AuditEventEntityComponent::getQuery) + .filter(Objects::nonNull) + .count()); + } + + @Test + public void testSendIti66WithPatientReference() { + var result = sendViaProducer(listPatientReferenceParameter(), statusParameter()); + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (ListResource) result.getEntry().get(0).getResource(); + assertEquals("9bc72458-49b0-11e6-8a1c-3c1620524153", p.getIdElement().getIdPart()); + assertEquals(2, FhirAuditRepository.getAuditEvents().size()); + } + + private Optional findRoleAgentWithCode(AuditEvent auditEvent, String code) { + return auditEvent.getAgent().stream() + .filter(p -> p.getType().getCodingFirstRep().getCode().equals(code)) + .findFirst(); + } +} \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java new file mode 100644 index 0000000000..3428b3cacd --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti67/TestIti67WithBalpAudit.java @@ -0,0 +1,111 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti67; + +import org.hl7.fhir.r4.model.AuditEvent; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DocumentReference; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti67WithBalpAudit extends AbstractTestIti67 { + + private static final String CONTEXT_DESCRIPTOR = "iti-67-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testSendManualIti67() { + var result = sendManually(referencePatientIdentifierParameter()); + + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (DocumentReference) result.getEntry().get(0).getResource(); + assertEquals("63ab1c29-4225-11e6-9b33-0050569b0094", p.getIdElement().getIdPart()); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(1, auditEvents.size()); + var auditEvent = auditEvents.get(0); + + assertEquals("110112", auditEvent.getType().getCode()); + assertEquals("ITI-67", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("E", auditEvent.getAction().toCode()); + assertEquals("Query", auditEvent.getType().getDisplay()); + assertEquals("0", auditEvent.getOutcome().toCode()); + Optional sourceRole = findRoleAgentWithCode(auditEvent, "110153"); + assertTrue(sourceRole.isPresent()); + assertTrue(sourceRole.get().getRequestor()); + Optional destinationRole = findRoleAgentWithCode(auditEvent, "110152"); + assertTrue(destinationRole.isPresent()); + assertFalse(destinationRole.get().getRequestor()); + assertEquals(1, auditEvent.getEntity().stream() + .filter(event -> event.getType().getCode().equals("2") && event.getRole().getCode().equals("24")) + .map(AuditEvent.AuditEventEntityComponent::getQuery) + .filter(Objects::nonNull) + .count()); + } + + @Test + public void testSendEndpointIti67() { + sendViaProducer(referencePatientIdentifierParameter()); + assertEquals(2, FhirAuditRepository.getAuditEvents().size()); + + List queries = FhirAuditRepository.getAuditEvents().stream().flatMap(event -> event.getEntity().stream() + .filter(entity -> entity.getType().getCode().equals("2") && entity.getRole().getCode().equals("24")) + .map(AuditEvent.AuditEventEntityComponent::getQuery) + .filter(Objects::nonNull)).collect(Collectors.toList()); + + String query = new String(queries.get(0), StandardCharsets.UTF_8); + assertTrue(query.startsWith("patient.identifier=urn:oid:2.16.840.1.113883.3.37.4.1.1.2.1.1|1")); + } + + private Optional findRoleAgentWithCode(AuditEvent auditEvent, String code) { + return auditEvent.getAgent().stream() + .filter(p -> p.getType().getCodingFirstRep().getCode().equals(code)) + .findFirst(); + } + +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java new file mode 100644 index 0000000000..d04c5e665b --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti68/TestIti68WithBalpAudit.java @@ -0,0 +1,65 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openehealth.ipf.platform.camel.ihe.fhir.iti68; + +import ca.uhn.fhir.rest.gclient.ICriterion; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestIti68WithBalpAudit extends AbstractTestIti68 { + + private static final String CONTEXT_DESCRIPTOR = "iti-68-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + public void testRetrieveDocument() { + var response = sendViaProducer((ICriterion) null); + assertArrayEquals(Iti68TestRouteBuilder.DATA, response); + + // Check ATNA Audit + var auditEvents = FhirAuditRepository.getAuditEvents(); + assertEquals(1, auditEvents.size()); + var auditEvent = auditEvents.get(0); + + assertEquals("110106", auditEvent.getType().getCode()); + assertEquals("ITI-68", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("C", auditEvent.getAction().toCode()); + assertEquals("Export", auditEvent.getType().getDisplay()); + assertEquals("0", auditEvent.getOutcome().toCode()); + } + + +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java new file mode 100644 index 0000000000..7fc0f2be73 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/pharm5/TestPharm5WithBalpAudit.java @@ -0,0 +1,81 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openehealth.ipf.platform.camel.ihe.fhir.pharm5; + +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.DocumentReference; +import org.hl7.fhir.r4.model.ResourceType; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.openehealth.ipf.commons.ihe.fhir.extension.FhirAuditRepository; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * + */ +@ExtendWith(FhirAuditRepository.class) +public class TestPharm5WithBalpAudit extends AbstractTestPharm5 { + private static final String CONTEXT_DESCRIPTOR = "pharm-5-balp.xml"; + + @BeforeAll + public static void setUpClass() { + startServer(CONTEXT_DESCRIPTOR); + } + + @BeforeEach + public void beforeEach() { + FhirAuditRepository.getAuditEvents().clear(); + } + + @Test + void testPharm5FindMedicationTreatmentPlans() { + var result = sendManually(findMedicationTreatmentPlanParameters()); + + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (DocumentReference) result.getEntry().get(0).getResource(); + assertEquals("63ab1c29-4225-11e6-9b33-0050569b0094", p.getIdElement().getIdPart()); + assertEquals("$find-medication-treatment-plans", p.getDescription()); + + // Check ATNA Audit + assertEquals(1, FhirAuditRepository.getAuditEvents().size()); + var auditEvent = FhirAuditRepository.getAuditEvents().get(0); + assertEquals("110112", auditEvent.getType().getCode()); + assertEquals("PHARM-5", auditEvent.getSubtypeFirstRep().getCode()); + assertEquals("E", auditEvent.getAction().toCode()); + assertEquals("Query", auditEvent.getType().getDisplay()); + assertEquals("0", auditEvent.getOutcome().toCode()); + } + + @Test + void testSendPharm5FindMedicationList() { + var result = sendManually(findMedicationListParameters()); + assertEquals(Bundle.BundleType.SEARCHSET, result.getType()); + assertEquals(ResourceType.Bundle, result.getResourceType()); + assertEquals(1, result.getTotal()); + + var p = (DocumentReference) result.getEntry().get(0).getResource(); + assertEquals("63ab1c29-4225-11e6-9b33-0050569b0094", p.getIdElement().getIdPart()); + assertEquals("$find-medication-list", p.getDescription()); + + assertEquals(1, FhirAuditRepository.getAuditEvents().size()); + } +} diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml new file mode 100644 index 0000000000..fbe481eb32 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/common-fhir-balp-beans.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-105-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-105-balp.xml new file mode 100644 index 0000000000..57d8d32241 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-105-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-65-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-65-balp.xml new file mode 100644 index 0000000000..75c8176d30 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-65-balp.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-67-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-67-balp.xml new file mode 100644 index 0000000000..0362f3ee28 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-67-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-68-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-68-balp.xml new file mode 100644 index 0000000000..d8c167de39 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/iti-68-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/pharm-5-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/pharm-5-balp.xml new file mode 100644 index 0000000000..03f58ed71d --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/pharm-5-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/mhd/src/test/resources/v421/iti-66-balp.xml b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/v421/iti-66-balp.xml new file mode 100644 index 0000000000..641c177e88 --- /dev/null +++ b/platform-camel/ihe/fhir/r4/mhd/src/test/resources/v421/iti-66-balp.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/fhir/r4/pixpdq/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti78/TestIti78Success.java b/platform-camel/ihe/fhir/r4/pixpdq/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti78/TestIti78Success.java index 4331bb915d..1ee28ae12f 100644 --- a/platform-camel/ihe/fhir/r4/pixpdq/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti78/TestIti78Success.java +++ b/platform-camel/ihe/fhir/r4/pixpdq/src/test/java/org/openehealth/ipf/platform/camel/ihe/fhir/iti78/TestIti78Success.java @@ -25,6 +25,7 @@ import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirEventTypeCode; import org.openehealth.ipf.commons.ihe.fhir.audit.codes.FhirParticipantObjectIdTypeCode; import org.openehealth.ipf.commons.ihe.fhir.iti78.PdqPatient; +import org.openehealth.ipf.commons.ihe.fhir.support.audit.marshal.FhirAuditJsonSerializationStrategy; import java.nio.charset.StandardCharsets; @@ -107,6 +108,9 @@ public void testSendManualPdqm() { assertEquals(FhirParticipantObjectIdTypeCode.MobilePatientDemographicsQuery, patient.getParticipantObjectIDTypeCode()); + var fhir = new FhirAuditJsonSerializationStrategy(serverFhirContext).marshal(event, true); + System.out.println(fhir); + } @Test @@ -126,6 +130,9 @@ public void testGetResource() { assertEquals(ParticipantObjectTypeCode.Person, patient.getParticipantObjectTypeCode()); assertEquals(ParticipantObjectTypeCodeRole.Patient, patient.getParticipantObjectTypeCodeRole()); assertEquals("Patient/4711", patient.getParticipantObjectID()); + + var fhir = new FhirAuditJsonSerializationStrategy(serverFhirContext).marshal(event, true); + System.out.println(fhir); } diff --git a/pom.xml b/pom.xml index 6b4afd1b8a..7e9a5202c4 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,7 @@ 5.15.0 0.6 9.0.83 + 2.2.28.Final 4.24 @@ -260,6 +261,12 @@ mockserver-client-java ${mockserver-version} + + io.undertow + undertow-servlet + ${undertow-version} + test +