diff --git a/commons/ihe/pom.xml b/commons/ihe/pom.xml index 87eac3e5a4..e4d932bd4a 100644 --- a/commons/ihe/pom.xml +++ b/commons/ihe/pom.xml @@ -30,6 +30,7 @@ fhir hpd xacml20 + svs diff --git a/commons/ihe/svs/pom.xml b/commons/ihe/svs/pom.xml new file mode 100644 index 0000000000..7bbd01941e --- /dev/null +++ b/commons/ihe/svs/pom.xml @@ -0,0 +1,125 @@ + + + 4.0.0 + ipf-commons-ihe-svs + ipf-commons-ihe-svs + SVS-specific IHE support + + + org.openehealth.ipf.commons + ipf-commons-ihe + 5.0-SNAPSHOT + + + + + + org.openehealth.ipf.commons + ipf-commons-ihe-core + + + org.openehealth.ipf.commons + ipf-commons-ihe-ws + + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + + + org.openehealth.ipf.commons + ipf-commons-xml + + + + + + generate-stubs + + + + org.projectlombok + lombok-maven-plugin + + + generate-sources + + delombok + + + + + UTF-8 + false + ${project.basedir}/src/main/java + ${output-folder}/generated-stubs + + + + org.projectlombok + lombok + ${lombok-version} + + + + + + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.codehaus.mojo + build-helper-maven-plugin + + + add-source + none + + + add-test-source + none + + + + + org.projectlombok + lombok-maven-plugin + + + generate-sources + + delombok + + + + + UTF-8 + false + ${project.basedir}/src/main/java + ${project.build.directory}/generated-stubs + + + + org.projectlombok + lombok + ${lombok-version} + + + + + + + \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/SVS.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/SVS.java new file mode 100644 index 0000000000..8c72781af2 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/SVS.java @@ -0,0 +1,72 @@ +/* + * Copyright 2025 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.svs; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.openehealth.ipf.commons.ihe.core.IntegrationProfile; +import org.openehealth.ipf.commons.ihe.core.InteractionId; +import org.openehealth.ipf.commons.ihe.svs.core.audit.SvsAuditDataset; +import org.openehealth.ipf.commons.ihe.svs.iti48.Iti48AuditStrategy; +import org.openehealth.ipf.commons.ihe.svs.iti48.Iti48PortType; +import org.openehealth.ipf.commons.ihe.ws.WsInteractionId; +import org.openehealth.ipf.commons.ihe.ws.WsTransactionConfiguration; + +import javax.xml.namespace.QName; +import java.util.Arrays; +import java.util.List; + +/** + * Definitions for the Sharing Value Sets (SVS) integration profile of the IHE TF. + * + * @author Quentin Ligier + * @since 5.0 + */ +public class SVS implements IntegrationProfile { + + private static final SVS Instance = new SVS(); + + @AllArgsConstructor + public enum Interactions implements WsInteractionId> { + ITI_48(ITI_48_WS_CONFIG); + + @Getter + private final WsTransactionConfiguration wsTransactionConfiguration; + } + + @Override + public List getInteractionIds() { + return Arrays.asList(Interactions.values()); + } + + private static final WsTransactionConfiguration ITI_48_WS_CONFIG = new WsTransactionConfiguration<>( + "svs-iti48", + "Retrieve Value Set", + true, + new Iti48AuditStrategy(false), + new Iti48AuditStrategy(true), + new QName("urn:ihe:iti:svs:2008", "ValueSetRepository_Service", "ihe"), + Iti48PortType.class, + new QName("urn:ihe:iti:svs:2008", "ValueSetRepository_Binding_Soap12", "ihe"), + false, + "wsdl/iti48/iti48.wsdl", + true, + false, + false, + false + ); +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/SvsException.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/SvsException.java new file mode 100644 index 0000000000..1bed21dcb2 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/SvsException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 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.svs.core; + +/** + * @author Quentin Ligier + **/ +public class SvsException extends RuntimeException { + + public SvsException(Throwable cause) { + super(cause); + } + + public SvsException(String message) { + super(message); + } + + public SvsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/SvsValidator.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/SvsValidator.java new file mode 100644 index 0000000000..42e66a1e81 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/SvsValidator.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 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.svs.core; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.util.JAXBSource; +import org.openehealth.ipf.commons.core.modules.api.ValidationException; +import org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest; +import org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse; +import org.openehealth.ipf.commons.xml.XsdValidator; + +import javax.xml.transform.Source; + +/** + * @author Quentin Ligier + **/ +public class SvsValidator { + public static final JAXBContext JAXB_CONTEXT; + static { + try { + JAXB_CONTEXT = JAXBContext.newInstance( + org.openehealth.ipf.commons.ihe.svs.core.requests.ObjectFactory.class, + org.openehealth.ipf.commons.ihe.svs.core.responses.ObjectFactory.class); + } catch (JAXBException e) { + throw new ExceptionInInitializerError(e); + } + } + + private static final XsdValidator XSD_VALIDATOR = new XsdValidator(); + + public static void validateIti48Request(final RetrieveValueSetRequest request) { + validateWithXsd(request, "/wsdl/iti48/SVS.xsd"); + // Not much to do here + } + + public static void validateIti48Response(final RetrieveValueSetResponse response) { + validateWithXsd(response, "/wsdl/iti48/SVS.xsd"); + // Not much to do here + } + + private static void validateWithXsd(final Object object, final String schemaName) { + try { + final Source source = new JAXBSource(JAXB_CONTEXT, object); + XSD_VALIDATOR.validate(source, schemaName); + } catch (Exception e) { + throw new SvsException(e); + } + } +} diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/audit/SvsAuditDataset.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/audit/SvsAuditDataset.java new file mode 100644 index 0000000000..995301a763 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/audit/SvsAuditDataset.java @@ -0,0 +1,56 @@ +/* + * Copyright 2025 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.svs.core.audit; + +import lombok.*; +import org.openehealth.ipf.commons.ihe.ws.cxf.audit.WsAuditDataset; + +import java.io.Serial; + +/** + * The audit dataset for SVS transactions. + * + * @author Quentin Ligier + */ +@Getter +@Setter +@ToString(callSuper = true) +public class SvsAuditDataset extends WsAuditDataset { + + @Serial + private static final long serialVersionUID = -8950614248765744542L; + + /** + * The retrieved value set ID (OID). + */ + private String valueSetId; + + /** + * The retrieved value set display name, if any. + */ + private String valueSetName; + + /** + * The retrieved value set version, if any. + */ + private String valueSetVersion; + + public SvsAuditDataset(boolean serverSide) { + super(serverSide); + this.setSourceUserIsRequestor(false); + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/audit/SvsEventTypeCode.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/audit/SvsEventTypeCode.java new file mode 100644 index 0000000000..8c299e942f --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/audit/SvsEventTypeCode.java @@ -0,0 +1,38 @@ +/* + * Copyright 2025 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.svs.core.audit; + +import lombok.Getter; +import org.openehealth.ipf.commons.audit.types.EnumeratedCodedValue; +import org.openehealth.ipf.commons.audit.types.EventType; + +/** + * EventTypes for the SVS module + * + * @author Quentin Ligier + */ +public enum SvsEventTypeCode implements EventType, EnumeratedCodedValue { + + RetrieveValueSet("ITI-48", "Retrieve Value Set"); + + @Getter + private final EventType value; + + SvsEventTypeCode(String code, String displayName) { + this.value = EventType.of(code, "IHE Transactions", displayName); + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/jaxbadapters/OffsetDateTimeAdapter.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/jaxbadapters/OffsetDateTimeAdapter.java new file mode 100644 index 0000000000..a664116462 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/jaxbadapters/OffsetDateTimeAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright 2025 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.svs.core.jaxbadapters; + +import jakarta.xml.bind.DatatypeConverter; +import jakarta.xml.bind.annotation.adapters.XmlAdapter; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.format.DateTimeFormatter; +import java.util.GregorianCalendar; + +/** + * A JAXB converter between {@link String} and {@link OffsetDateTime}. + * + * @author Quentin Ligier + */ +public class OffsetDateTimeAdapter extends XmlAdapter { + + @Override + public OffsetDateTime unmarshal(final String value) { + if (value == null) { + return null; + } + var calendar = DatatypeConverter.parseDateTime(value); + if (calendar instanceof GregorianCalendar) { + return ((GregorianCalendar) calendar).toZonedDateTime().toOffsetDateTime(); + } + else { + return OffsetDateTime.ofInstant(Instant.ofEpochMilli(calendar.getTimeInMillis()), + calendar.getTimeZone().toZoneId()); + } + } + + @Override + public String marshal(final OffsetDateTime value) { + return (value != null) ? DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(value) : null; + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/ObjectFactory.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/ObjectFactory.java new file mode 100644 index 0000000000..4c1fe3d4ad --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/ObjectFactory.java @@ -0,0 +1,85 @@ +/* + * Copyright 2025 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.svs.core.requests; + +import jakarta.xml.bind.JAXBElement; +import jakarta.xml.bind.annotation.XmlElementDecl; +import jakarta.xml.bind.annotation.XmlRegistry; +import javax.xml.namespace.QName; + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the org.openehealth.ipf.commons.ihe.hpd.stub.chpidd package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + private final static QName _RetrieveValueSetRequest_QNAME = new QName("urn:ihe:iti:svs:2008", "RetrieveValueSetRequest"); + private final static QName _ValueSetRequest_QNAME = new QName("urn:ihe:iti:svs:2008", "ValueSetRequest"); + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: org.openehealth.ipf.commons.ihe.svs.core.requests + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link RetrieveValueSetRequest } + * + */ + public RetrieveValueSetRequest createRetrieveValueSetRequest() { + return new RetrieveValueSetRequest(); + } + + /** + * Create an instance of {@link ValueSetRequest } + * + */ + public ValueSetRequest createValueSetRequest() { + return new ValueSetRequest(); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link RetrieveValueSetRequest }{@code >}} + * + */ + @XmlElementDecl(namespace = "urn:ihe:iti:svs:2008", name = "downloadRequest") + public JAXBElement createRetrieveValueSetRequest(RetrieveValueSetRequest value) { + return new JAXBElement<>(_RetrieveValueSetRequest_QNAME, RetrieveValueSetRequest.class, null, value); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link ValueSetRequest }{@code >}} + * + */ + @XmlElementDecl(namespace = "urn:ihe:iti:svs:2008", name = "downloadResponse") + public JAXBElement createValueSetRequest(ValueSetRequest value) { + return new JAXBElement<>(_ValueSetRequest_QNAME, ValueSetRequest.class, null, value); + } + +} diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/RetrieveValueSetRequest.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/RetrieveValueSetRequest.java new file mode 100644 index 0000000000..c104bf1ced --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/RetrieveValueSetRequest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2025 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.svs.core.requests; + +import jakarta.xml.bind.annotation.*; +import lombok.Data; +import lombok.NonNull; + +/** + * Model of an SVS RetrieveValueSetRequest + * + * @author Quentin Ligier + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "RetrieveValueSetRequestType", propOrder = { "valueSet" }) +@XmlRootElement(name = "RetrieveValueSetRequest") +@Data +public class RetrieveValueSetRequest { + + @XmlElement(name = "ValueSet", required = true) + @NonNull + private ValueSetRequest valueSet; + + /** + * Empty constructor for JAXB. + */ + public RetrieveValueSetRequest() { + } + + public RetrieveValueSetRequest(@NonNull final ValueSetRequest valueSet) { + this.valueSet = valueSet; + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/ValueSetRequest.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/ValueSetRequest.java new file mode 100644 index 0000000000..ffd9b6bc5f --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/ValueSetRequest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 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.svs.core.requests; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlType; +import lombok.Data; +import lombok.NonNull; + +/** + * Model of an SVS ValueSetRequest + * + * @author Quentin Ligier + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "ValueSetRequestType") +@Data +public class ValueSetRequest { + + @XmlAttribute(name = "id", required = true) + @NonNull + private String id; + + @XmlAttribute(name = "lang", namespace = javax.xml.XMLConstants.XML_NS_URI) + private String lang; + + @XmlAttribute(name = "version") + private String version; + + /** + * Empty constructor for JAXB. + */ + public ValueSetRequest() { + } + + public ValueSetRequest(@NonNull final String id, + final String lang, + final String version) { + this.id = id; + this.lang = lang; + this.version = version; + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/package-info.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/package-info.java new file mode 100644 index 0000000000..33b153f1e4 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/requests/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2025 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. + */ + +@jakarta.xml.bind.annotation.XmlSchema(namespace = "urn:ihe:iti:svs:2008", elementFormDefault = jakarta.xml.bind.annotation.XmlNsForm.QUALIFIED) +package org.openehealth.ipf.commons.ihe.svs.core.requests; \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/Concept.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/Concept.java new file mode 100644 index 0000000000..58649ff7e2 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/Concept.java @@ -0,0 +1,72 @@ +/* + * Copyright 2025 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.svs.core.responses; + +import lombok.Data; +import lombok.NonNull; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlType; + +/** + * Model of an SVS Concept + * + * @author Quentin Ligier + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "CE") +@Data +public class Concept { + + @XmlAttribute(name = "code", required = true) + @NonNull + private String code; + + @XmlAttribute(name = "codeSystem", required = true) + @NonNull + private String codeSystem; + + @XmlAttribute(name = "displayName", required = true) + @NonNull + private String displayName; + + @XmlAttribute(name = "codeSystemName") + private String codeSystemName; + + @XmlAttribute(name = "codeSystemVersion") + private String codeSystemVersion; + + /** + * Empty constructor for JAXB. + */ + public Concept() { + } + + public Concept(@NonNull final String code, + @NonNull final String codeSystem, + @NonNull final String displayName, + final String codeSystemName, + final String codeSystemVersion) { + this.code = code; + this.codeSystem = codeSystem; + this.displayName = displayName; + this.codeSystemName = codeSystemName; + this.codeSystemVersion = codeSystemVersion; + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/ConceptList.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/ConceptList.java new file mode 100644 index 0000000000..2dfbc477fb --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/ConceptList.java @@ -0,0 +1,51 @@ +/* + * Copyright 2025 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.svs.core.responses; + +import lombok.Data; + +import jakarta.xml.bind.annotation.*; + +import java.util.ArrayList; +import java.util.List; + +/** + * Model of an SVS ConceptList + * + * @author Quentin Ligier + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "ConceptListType", propOrder = { "concept" }) +@Data +public class ConceptList { + + @XmlElement(name = "Concept", required = true) + private final List concept = new ArrayList<>(); + + @XmlAttribute(name = "lang", namespace = javax.xml.XMLConstants.XML_NS_URI) + private String lang; + + /** + * Empty constructor for JAXB. + */ + public ConceptList() { + } + + public ConceptList(final String lang) { + this.lang = lang; + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/ObjectFactory.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/ObjectFactory.java new file mode 100644 index 0000000000..39999d00e1 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/ObjectFactory.java @@ -0,0 +1,122 @@ +/* + * Copyright 2025 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.svs.core.responses; + +import jakarta.xml.bind.JAXBElement; +import jakarta.xml.bind.annotation.XmlElementDecl; +import jakarta.xml.bind.annotation.XmlRegistry; +import javax.xml.namespace.QName; + + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the org.openehealth.ipf.commons.ihe.hpd.stub.chpidd package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + private final static QName _Concept_QNAME = new QName("urn:ihe:iti:svs:2008", "Concept"); + private final static QName _ConceptList_QNAME = new QName("urn:ihe:iti:svs:2008", "ConceptList"); + private final static QName _RetrieveValueSetResponse_QNAME = new QName("urn:ihe:iti:svs:2008", "RetrieveValueSetResponse"); + private final static QName _ValueSetResponse_QNAME = new QName("urn:ihe:iti:svs:2008", "ValueSetResponse"); + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: org.openehealth.ipf.commons.ihe.svs.core.responses + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link Concept } + * + */ + public Concept createConcept() { + return new Concept(); + } + + /** + * Create an instance of {@link ConceptList } + * + */ + public ConceptList createConceptList() { + return new ConceptList(); + } + + /** + * Create an instance of {@link RetrieveValueSetResponse } + * + */ + public RetrieveValueSetResponse createRetrieveValueSetResponse() { + return new RetrieveValueSetResponse(); + } + + /** + * Create an instance of {@link ValueSetResponse } + * + */ + public ValueSetResponse createValueSetResponse() { + return new ValueSetResponse(); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link Concept }{@code >}} + * + */ + @XmlElementDecl(namespace = "urn:ihe:iti:svs:2008", name = "Concept") + public JAXBElement createConcept(Concept value) { + return new JAXBElement<>(_Concept_QNAME, Concept.class, null, value); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link ConceptList }{@code >}} + * + */ + @XmlElementDecl(namespace = "urn:ihe:iti:svs:2008", name = "ConceptList") + public JAXBElement createConceptList(ConceptList value) { + return new JAXBElement<>(_ConceptList_QNAME, ConceptList.class, null, value); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link RetrieveValueSetResponse }{@code >}} + * + */ + @XmlElementDecl(namespace = "urn:ihe:iti:svs:2008", name = "RetrieveValueSetResponse") + public JAXBElement createRetrieveValueSetResponse(RetrieveValueSetResponse value) { + return new JAXBElement<>(_RetrieveValueSetResponse_QNAME, RetrieveValueSetResponse.class, null, value); + } + + /** + * Create an instance of {@link JAXBElement }{@code <}{@link ValueSetResponse }{@code >}} + * + */ + @XmlElementDecl(namespace = "urn:ihe:iti:svs:2008", name = "ValueSetResponse") + public JAXBElement createValueSetResponse(ValueSetResponse value) { + return new JAXBElement<>(_ValueSetResponse_QNAME, ValueSetResponse.class, null, value); + } + +} diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/RetrieveValueSetResponse.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/RetrieveValueSetResponse.java new file mode 100644 index 0000000000..04312962b5 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/RetrieveValueSetResponse.java @@ -0,0 +1,57 @@ +/* + * Copyright 2025 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.svs.core.responses; + +import lombok.Data; +import lombok.NonNull; +import org.openehealth.ipf.commons.ihe.svs.core.jaxbadapters.OffsetDateTimeAdapter; + +import jakarta.xml.bind.annotation.*; +import jakarta.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.OffsetDateTime; + +/** + * Model of an SVS RetrieveValueSetResponse + * + * @author Quentin Ligier + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "RetrieveValueSetResponseType", propOrder = {"valueSet"}) +@XmlRootElement(name = "RetrieveValueSetResponse") +@Data +public class RetrieveValueSetResponse { + + @XmlElement(name = "ValueSet", required = true) + @NonNull + private ValueSetResponse valueSet; + + @XmlAttribute(name = "cacheExpirationHint") + @XmlJavaTypeAdapter(OffsetDateTimeAdapter.class) + protected OffsetDateTime cacheExpirationHint; + + /** + * Empty constructor for JAXB. + */ + public RetrieveValueSetResponse() { + } + + public RetrieveValueSetResponse(@NonNull final ValueSetResponse valueSet, + final OffsetDateTime cacheExpirationHint) { + this.valueSet = valueSet; + this.cacheExpirationHint = cacheExpirationHint; + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/ValueSetResponse.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/ValueSetResponse.java new file mode 100644 index 0000000000..c136a1ac02 --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/ValueSetResponse.java @@ -0,0 +1,63 @@ +/* + * Copyright 2025 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.svs.core.responses; + +import lombok.Data; +import lombok.NonNull; + +import jakarta.xml.bind.annotation.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Model of an SVS ValueSetResponse + * + * @author Quentin Ligier + */ +@XmlAccessorType(XmlAccessType.FIELD) +@XmlType(name = "ValueSetResponseType", propOrder = { "conceptList" }) +@Data +public class ValueSetResponse { + + @XmlElement(name = "ConceptList", required = true) + private final List conceptList = new ArrayList<>(); + + @XmlAttribute(name = "id", required = true) + @NonNull + private String id; + + @XmlAttribute(name = "displayName", required = true) + @NonNull + private String displayName; + + @XmlAttribute(name = "version") + private String version; + + /** + * Empty constructor for JAXB. + */ + public ValueSetResponse() { + } + + public ValueSetResponse(@NonNull final String id, + @NonNull final String displayName, + final String version) { + this.id = id; + this.displayName = displayName; + this.version = version; + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/package-info.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/package-info.java new file mode 100644 index 0000000000..70b6a7baea --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/core/responses/package-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2025 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. + */ + +@jakarta.xml.bind.annotation.XmlSchema(namespace = "urn:ihe:iti:svs:2008", elementFormDefault = jakarta.xml.bind.annotation.XmlNsForm.QUALIFIED) +package org.openehealth.ipf.commons.ihe.svs.core.responses; \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/iti48/Iti48AuditStrategy.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/iti48/Iti48AuditStrategy.java new file mode 100644 index 0000000000..d1873d90dc --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/iti48/Iti48AuditStrategy.java @@ -0,0 +1,204 @@ +/* + * Copyright 2025 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.svs.iti48; + +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.codes.*; +import org.openehealth.ipf.commons.audit.event.BaseAuditMessageBuilder; +import org.openehealth.ipf.commons.audit.model.AuditMessage; +import org.openehealth.ipf.commons.audit.model.TypeValuePairType; +import org.openehealth.ipf.commons.ihe.core.atna.AuditStrategySupport; +import org.openehealth.ipf.commons.ihe.svs.core.audit.SvsAuditDataset; +import org.openehealth.ipf.commons.ihe.svs.core.audit.SvsEventTypeCode; +import org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest; +import org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static org.openehealth.ipf.commons.audit.utils.AuditUtils.getProcessId; + +/** + * Audit strategy for ITI-48. + * + * @author Quentin Ligier + */ +public class Iti48AuditStrategy extends AuditStrategySupport { + + /** + * @param serverSide true when this strategy is a server-side one; + * false otherwise. + */ + public Iti48AuditStrategy(final boolean serverSide) { + super(serverSide); + } + + /** + * Creates a new audit dataset instance. + */ + @Override + public SvsAuditDataset createAuditDataset() { + return new SvsAuditDataset(this.isServerSide()); + } + + /** + * Enriches the given audit dataset with transaction-specific contents of the request message and Camel exchange. + * + * @param auditDataset audit dataset to be enriched. + * @param request {@link Object} representing the request. + * @param parameters additional parameters + */ + @Override + public SvsAuditDataset enrichAuditDatasetFromRequest(final SvsAuditDataset auditDataset, + final Object request, + final Map parameters) { + if (request instanceof final RetrieveValueSetRequest rvsRequest) { + auditDataset.setValueSetId(rvsRequest.getValueSet().getId()); + auditDataset.setValueSetVersion(rvsRequest.getValueSet().getVersion()); + } + return auditDataset; + } + + /** + * Enriches the given audit dataset with transaction-specific contents of the response message. + * + * @param auditDataset audit dataset to be enriched. + * @param response {@link Object} representing the responded resource. + * @param auditContext audit context, if relevant. + * @return true if response indicates success, false otherwise + */ + @Override + public boolean enrichAuditDatasetFromResponse(final SvsAuditDataset auditDataset, + final Object response, + final AuditContext auditContext) { + if (response instanceof final RetrieveValueSetResponse rvsResponse) { + auditDataset.setValueSetId(rvsResponse.getValueSet().getId()); + auditDataset.setValueSetVersion(rvsResponse.getValueSet().getVersion()); + auditDataset.setValueSetName(rvsResponse.getValueSet().getDisplayName()); + auditDataset.setEventOutcomeIndicator(EventOutcomeIndicator.Success); + } + return true; + } + + /** + * Constructs an {@link AuditMessage} from a provided {@link SvsAuditDataset} + * + * @param auditContext audit context + * @param auditDataset audit dataset + * @return audit messages + */ + @Override + public AuditMessage[] makeAuditMessage(final AuditContext auditContext, + final SvsAuditDataset auditDataset) { + var builder = new SvsAuditMessageBuilder(); + builder.setEventIdentification( + auditDataset.getEventOutcomeIndicator(), + auditDataset.getEventOutcomeDescription(), + auditDataset.isServerSide() ? EventActionCode.Read : EventActionCode.Create, + auditDataset.isServerSide() ? EventIdCode.Export : EventIdCode.Import, + SvsEventTypeCode.RetrieveValueSet, + auditDataset.getPurposesOfUse() + ); + + // Beware: the 'source' participant describes the Value Set Repository + // the 'destination' participant describes the Value Set Consumer + // The AuditMessage source and destination are inverted compared to the audit dataset + + if (auditDataset.isServerSide()) { + // The destination UserId is required but unspecified in the ITI-48 specification + final var destinationUserId = Stream.of(auditDataset.getSourceUserId(), + auditDataset.getSourceUserName(), + auditDataset.getRemoteAddress()) + .filter(Objects::nonNull) + .findFirst() + .orElseGet(auditContext::getAuditValueIfMissing); + + builder.addSourceActiveParticipant( + getProcessId(), + auditDataset.getDestinationUserId(), + null, + auditDataset.getLocalAddress(), + auditDataset.isSourceUserIsRequestor() + ); + builder.addDestinationActiveParticipant( + destinationUserId, + auditDataset.getSourceUserName(), + null, + auditDataset.getRemoteAddress(), + !auditDataset.isSourceUserIsRequestor() + ); + } else { + // The destination UserId is required but unspecified in the ITI-48 specification + final var destinationUserId = auditDataset.getSourceUserId() != null + ? auditDataset.getSourceUserId() + : getProcessId(); // The process ID is the AlternativeUserID + + builder.addSourceActiveParticipant( + auditDataset.getDestinationUserId(), + null, + null, + auditDataset.getRemoteAddress(), + auditDataset.isSourceUserIsRequestor() + ); + builder.addDestinationActiveParticipant( + destinationUserId, + getProcessId(), + null, + auditDataset.getLocalAddress(), + !auditDataset.isSourceUserIsRequestor() + ); + } + for (var humanUser : auditDataset.getHumanUsers()) { + if (!humanUser.isEmpty()) { + builder.addActiveParticipant( + humanUser.getId() != null ? + humanUser.getId() : + auditContext.getAuditValueIfMissing(), + null, + humanUser.getName(), + false, + humanUser.getRoles(), + null); + } + } + if (auditContext != null) { + builder.setAuditSource(auditContext); + } + if (auditDataset.getValueSetId() != null) { + builder.addParticipantObjectIdentification( + ParticipantObjectIdTypeCode.SearchCriteria, + auditDataset.getValueSetName(), + null, + auditDataset.getValueSetVersion() != null + ? List.of(new TypeValuePairType("ValueSetVersion", auditDataset.getValueSetVersion())) + : null, + auditDataset.getValueSetId(), + ParticipantObjectTypeCode.System, + ParticipantObjectTypeCodeRole.Report, + null, + null + ); + } + + return builder.getMessages(); + } + + private static class SvsAuditMessageBuilder extends BaseAuditMessageBuilder { + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/iti48/Iti48PortType.java b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/iti48/Iti48PortType.java new file mode 100644 index 0000000000..18b3b78a3a --- /dev/null +++ b/commons/ihe/svs/src/main/java/org/openehealth/ipf/commons/ihe/svs/iti48/Iti48PortType.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 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.svs.iti48; + +import org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest; +import org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse; + +import jakarta.jws.WebMethod; +import jakarta.jws.WebParam; +import jakarta.jws.WebResult; +import jakarta.jws.WebService; +import jakarta.jws.soap.SOAPBinding; +import jakarta.xml.ws.Action; + +/** + * Provides the ITI-48 web-service interface (SOAP binding). + */ +@WebService(targetNamespace = "urn:ihe:iti:svs:2008", name = "ValueSetRepository_PortType", portName = "ValueSetRepository_Port_Soap12") +@SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE) +public interface Iti48PortType { + + /** + * Performs a stored query according to the ITI-48 specification. + * + * @param body The query request. + * @return the query response. + */ + @WebResult(name = "RetrieveValueSetResponse", + targetNamespace = "urn:ihe:iti:svs:2008", + partName = "body") + @Action(input = "urn:ihe:iti:svs:2008:RetrieveValueSet", + output = "urn:ihe:iti:svs:2008:RetrieveValueSetResponse") + @WebMethod(operationName = "ValueSetRepository_RetrieveValueSet") + RetrieveValueSetResponse valueSetRepositoryRetrieveValueSet( + @WebParam(partName = "body", + name = "RetrieveValueSetRequest", + targetNamespace = "urn:ihe:iti:svs:2008") + final RetrieveValueSetRequest body + ); +} \ No newline at end of file diff --git a/commons/ihe/svs/src/main/resources/wsdl/iti48/SVS.xsd b/commons/ihe/svs/src/main/resources/wsdl/iti48/SVS.xsd new file mode 100644 index 0000000000..e9f282729f --- /dev/null +++ b/commons/ihe/svs/src/main/resources/wsdl/iti48/SVS.xsd @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/commons/ihe/svs/src/main/resources/wsdl/iti48/iti48.wsdl b/commons/ihe/svs/src/main/resources/wsdl/iti48/iti48.wsdl new file mode 100644 index 0000000000..0f05327022 --- /dev/null +++ b/commons/ihe/svs/src/main/resources/wsdl/iti48/iti48.wsdl @@ -0,0 +1,50 @@ + + + + IHE SVS Value Set Repository = ITI-48 adaptor (SOAP) = Retrieve Value Set + + + + + + + + Retrieve Value Set + + + + Retrieve Value Set Response + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/commons/ihe/svs/src/main/resources/wsdl/iti48/xml.xsd b/commons/ihe/svs/src/main/resources/wsdl/iti48/xml.xsd new file mode 100644 index 0000000000..af6bfa0099 --- /dev/null +++ b/commons/ihe/svs/src/main/resources/wsdl/iti48/xml.xsd @@ -0,0 +1,285 @@ + + + + + +

+

About the XML namespace

+ +
+

+ This schema document describes the XML namespace, in a form + suitable for import by other schema documents. +

+

+ See + http://www.w3.org/XML/1998/namespace.html and + + http://www.w3.org/TR/REC-xml for information + about this namespace. +

+

+ Note that local names in this namespace are intended to be + defined only by the World Wide Web Consortium or its subgroups. + The names currently defined in this namespace are listed below. + They should not be used with conflicting semantics by any Working + Group, specification, or document instance. +

+

+ See further below in this document for more information about how to refer to this schema document from your own + XSD schema documents and about the + namespace-versioning policy governing this schema document. +

+
+
+ + + + + + +
+ +

lang (as an attribute name)

+

+ denotes an attribute whose value + is a language code for the natural language of the content of + any element; its value is inherited. This name is reserved + by virtue of its definition in the XML specification.

+ +
+
+

Notes

+

+ Attempting to install the relevant ISO 2- and 3-letter + codes as the enumerated possible values is probably never + going to be a realistic possibility. +

+

+ See BCP 47 at + http://www.rfc-editor.org/rfc/bcp/bcp47.txt + and the IANA language subtag registry at + + http://www.iana.org/assignments/language-subtag-registry + for further information. +

+

+ The union allows for the 'un-declaration' of xml:lang with + the empty string. +

+
+
+
+ + + + + + + + + +
+ + + + +
+ +

space (as an attribute name)

+

+ denotes an attribute whose + value is a keyword indicating what whitespace processing + discipline is intended for the content of the element; its + value is inherited. This name is reserved by virtue of its + definition in the XML specification.

+ +
+
+
+ + + + + + +
+ + + +
+ +

base (as an attribute name)

+

+ denotes an attribute whose value + provides a URI to be used as the base for interpreting any + relative URIs in the scope of the element on which it + appears; its value is inherited. This name is reserved + by virtue of its definition in the XML Base specification.

+ +

+ See http://www.w3.org/TR/xmlbase/ + for information about this attribute. +

+
+
+
+
+ + + + +
+ +

id (as an attribute name)

+

+ denotes an attribute whose value + should be interpreted as if declared to be of type ID. + This name is reserved by virtue of its definition in the + xml:id specification.

+ +

+ See http://www.w3.org/TR/xml-id/ + for information about this attribute. +

+
+
+
+
+ + + + + + + + + + +
+ +

Father (in any context at all)

+ +
+

+ denotes Jon Bosak, the chair of + the original XML Working Group. This name is reserved by + the following decision of the W3C XML Plenary and + XML Coordination groups: +

+
+

+ In appreciation for his vision, leadership and + dedication the W3C XML Plenary on this 10th day of + February, 2000, reserves for Jon Bosak in perpetuity + the XML name "xml:Father". +

+
+
+
+
+
+ + + +
+

About this schema document

+ +
+

+ This schema defines attributes and an attribute group suitable + for use by schemas wishing to allow xml:base, + xml:lang, xml:space or + xml:id attributes on elements they define. +

+

+ To enable this, such a schema must import this schema for + the XML namespace, e.g. as follows: +

+
+                        <schema . . .>
+                        . . .
+                        <import namespace="http://www.w3.org/XML/1998/namespace"
+                        schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+                    
+

+ or +

+
+                        <import namespace="http://www.w3.org/XML/1998/namespace"
+                        schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+                    
+

+ Subsequently, qualified reference to any of the attributes or the + group defined below will have the desired effect, e.g. +

+
+                        <type . . .>
+                        . . .
+                        <attributeGroup ref="xml:specialAttrs"/>
+                    
+

+ will define a type which will schema-validate an instance element + with any of those attributes. +

+
+
+
+
+ + + +
+

Versioning policy for this schema document

+
+

+ In keeping with the XML Schema WG's standard versioning + policy, this schema document will persist at + + http://www.w3.org/2009/01/xml.xsd. +

+

+ At the date of issue it can also be found at + + http://www.w3.org/2001/xml.xsd. +

+

+ The schema document at that URI may however change in the future, + in order to remain compatible with the latest version of XML + Schema itself, or with the XML namespace itself. In other words, + if the XML Schema or XML namespaces change, the version of this + document at + http://www.w3.org/2001/xml.xsd + + will change accordingly; the version at + + http://www.w3.org/2009/01/xml.xsd + + will not change. +

+

+ Previous dated (and unchanging) versions of this schema + document are at: +

+ +
+
+
+
+ + \ No newline at end of file diff --git a/commons/ihe/svs/src/test/java/org/openehealth/ipf/commons/ihe/svs/core/jaxbadapters/OffsetDateTimeAdapterTest.java b/commons/ihe/svs/src/test/java/org/openehealth/ipf/commons/ihe/svs/core/jaxbadapters/OffsetDateTimeAdapterTest.java new file mode 100644 index 0000000000..1492d103c5 --- /dev/null +++ b/commons/ihe/svs/src/test/java/org/openehealth/ipf/commons/ihe/svs/core/jaxbadapters/OffsetDateTimeAdapterTest.java @@ -0,0 +1,50 @@ +/* + * Copyright 2025 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.svs.core.jaxbadapters; + +import org.junit.jupiter.api.Test; + +import java.time.OffsetDateTime; +import java.util.regex.Pattern; + +import static org.junit.jupiter.api.Assertions.*; + +class OffsetDateTimeAdapterTest { + + private final OffsetDateTimeAdapter adapter = new OffsetDateTimeAdapter(); + + /** + * Simple pattern for an XSD:dateTime + */ + private final Pattern dateTimePattern = Pattern.compile("-?\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?([+-]\\d{2}:\\d{2}|Z)?"); + + @Test + public void testUnmarshal() { + assertNotNull(adapter.unmarshal("2007-12-03T10:15:30+01:00")); + assertNotNull(adapter.unmarshal("2002-10-10T17:00:00Z")); + assertNotNull(adapter.unmarshal("2001-10-26T21:32:52.12679")); + assertNotNull(adapter.unmarshal("2001-10-26T21:32:52.1267946-00:00")); + assertNotNull(adapter.unmarshal("2001-10-26T21:32:52")); + assertNotNull(adapter.unmarshal("-2001-10-26T21:32:52")); + } + + @Test + public void testMarshal() { + assertTrue(dateTimePattern.matcher(adapter.marshal(OffsetDateTime.now())).matches()); + assertNotNull(adapter.unmarshal(adapter.marshal(OffsetDateTime.now()))); + } +} \ No newline at end of file diff --git a/commons/ihe/svs/src/test/java/org/openehealth/ipf/commons/ihe/svs/iti48/Iti48AuditStrategyTest.java b/commons/ihe/svs/src/test/java/org/openehealth/ipf/commons/ihe/svs/iti48/Iti48AuditStrategyTest.java new file mode 100644 index 0000000000..9c849324ac --- /dev/null +++ b/commons/ihe/svs/src/test/java/org/openehealth/ipf/commons/ihe/svs/iti48/Iti48AuditStrategyTest.java @@ -0,0 +1,184 @@ +/* + * Copyright 2025 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.svs.iti48; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openehealth.ipf.commons.audit.AuditContext; +import org.openehealth.ipf.commons.audit.DefaultAuditContext; +import org.openehealth.ipf.commons.audit.codes.*; +import org.openehealth.ipf.commons.audit.model.ActiveParticipantType; +import org.openehealth.ipf.commons.audit.model.AuditMessage; +import org.openehealth.ipf.commons.audit.queue.RecordingAuditMessageQueue; +import org.openehealth.ipf.commons.audit.types.ActiveParticipantRoleId; +import org.openehealth.ipf.commons.audit.types.PurposeOfUse; +import org.openehealth.ipf.commons.audit.utils.AuditUtils; +import org.openehealth.ipf.commons.ihe.core.atna.AuditDataset; +import org.openehealth.ipf.commons.ihe.svs.core.audit.SvsAuditDataset; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class Iti48AuditStrategyTest { + private static final String USER_ID = "alias"; + private static final String USER_NAME = "Dr. Klaus-Peter Kohlrabi"; + private static final String SERVER_URI = "http://www.icw.int/svs/iti48-service"; + private static final String CLIENT_IP_ADDRESS = "141.44.162.126"; + private static final String VALUE_SET_ID = "1.2.840.10008.6.1.308"; + private static final String VALUE_SET_NAME = "Common Anatomic Regions Context ID 4031"; + private static final String VALUE_SET_VERSION = "20061023"; + + private static final PurposeOfUse[] PURPOSES_OF_USE = { + PurposeOfUse.of("12", "1.0.14265.1", "Law Enforcement"), + PurposeOfUse.of("13", "1.0.14265.1", "Something Else")}; + + private static final List USER_ROLES = List.of( + ActiveParticipantRoleId.of("ABC", "1.2.3.4.5", "Role_ABC"), + ActiveParticipantRoleId.of("DEF", "1.2.3.4.5.6", "Role_DEF")); + + private DefaultAuditContext auditContext; + private RecordingAuditMessageQueue recorder; + + @BeforeEach + public void setUp() { + auditContext = new DefaultAuditContext(); + recorder = new RecordingAuditMessageQueue(); + auditContext.setAuditMessageQueue(recorder); + } + + @AfterEach + public void tearDown() { + recorder.clear(); + } + + @Test + public void testServerSide() { + var strategy = new Iti48AuditStrategy(true); + var auditDataset = this.getHl7v3AuditDataset(strategy); + var auditMessage = this.makeAuditMessage(strategy, auditContext, auditDataset); + + assertNotNull(auditMessage); + auditMessage.validate(); + this.assertCommonAuditAttributes(auditMessage); + + assertEquals(EventIdCode.Export, auditMessage.getEventIdentification().getEventID()); + assertEquals(EventActionCode.Read, auditMessage.getEventIdentification().getEventActionCode()); + + final var source = this.getSourceParticipant(auditMessage); + assertEquals(AuditUtils.getProcessId(), source.getUserID()); + assertEquals(SERVER_URI, source.getAlternativeUserID()); + assertFalse(source.isUserIsRequestor()); + assertEquals(NetworkAccessPointTypeCode.IPAddress, source.getNetworkAccessPointTypeCode()); + + final var destination = this.getDestinationParticipant(auditMessage); + assertNotNull(destination.getUserID()); + assertTrue(destination.isUserIsRequestor()); + assertEquals(NetworkAccessPointTypeCode.IPAddress, destination.getNetworkAccessPointTypeCode()); + assertEquals(CLIENT_IP_ADDRESS, destination.getNetworkAccessPointID()); + } + + @Test + public void testClientSide() { + var strategy = new Iti48AuditStrategy(false); + var auditDataset = this.getHl7v3AuditDataset(strategy); + var auditMessage = this.makeAuditMessage(strategy, auditContext, auditDataset); + + assertNotNull(auditMessage); + auditMessage.validate(); + this.assertCommonAuditAttributes(auditMessage); + + assertEquals(EventIdCode.Import, auditMessage.getEventIdentification().getEventID()); + assertEquals(EventActionCode.Create, auditMessage.getEventIdentification().getEventActionCode()); + + final var source = this.getSourceParticipant(auditMessage); + assertEquals(SERVER_URI, source.getUserID()); + assertFalse(source.isUserIsRequestor()); + assertEquals(NetworkAccessPointTypeCode.IPAddress, source.getNetworkAccessPointTypeCode()); + assertEquals(CLIENT_IP_ADDRESS, source.getNetworkAccessPointID()); + + final var destination = this.getDestinationParticipant(auditMessage); + assertNotNull(destination.getUserID()); + assertEquals(AuditUtils.getProcessId(), destination.getAlternativeUserID()); + assertTrue(destination.isUserIsRequestor()); + assertEquals(NetworkAccessPointTypeCode.IPAddress, destination.getNetworkAccessPointTypeCode()); + } + + private SvsAuditDataset getHl7v3AuditDataset(final Iti48AuditStrategy strategy) { + var auditDataset = strategy.createAuditDataset(); + auditDataset.setEventOutcomeIndicator(EventOutcomeIndicator.Success); + auditDataset.setRemoteAddress(CLIENT_IP_ADDRESS); + auditDataset.setDestinationUserId(SERVER_URI); + auditDataset.setPurposesOfUse(PURPOSES_OF_USE); + auditDataset.getHumanUsers().add(new AuditDataset.HumanUser(USER_ID, USER_NAME, USER_ROLES)); + auditDataset.setValueSetId(VALUE_SET_ID); + auditDataset.setValueSetName(VALUE_SET_NAME); + auditDataset.setValueSetVersion(VALUE_SET_VERSION); + return auditDataset; + } + + private AuditMessage makeAuditMessage(Iti48AuditStrategy auditStrategy, + AuditContext auditContext, + SvsAuditDataset auditDataset) { + return auditStrategy.makeAuditMessage(auditContext, auditDataset)[0]; + } + + private ActiveParticipantType getSourceParticipant(AuditMessage auditMessage) { + return auditMessage.getActiveParticipants().stream() + .filter(apt -> ActiveParticipantRoleIdCode.Source == apt.getRoleIDCodes().get(0)) + .findFirst().orElseThrow(() -> new AssertionError("Expected source participant")); + } + + private ActiveParticipantType getDestinationParticipant(AuditMessage auditMessage) { + return auditMessage.getActiveParticipants().stream() + .filter(apt -> ActiveParticipantRoleIdCode.Destination == apt.getRoleIDCodes().get(0)) + .findFirst().orElseThrow(() -> new AssertionError("Expected destination active participant")); + } + + private void assertCommonAuditAttributes(AuditMessage auditMessage) { + assertEquals(EventOutcomeIndicator.Success, auditMessage.getEventIdentification().getEventOutcomeIndicator()); + final var eventTypeCode = auditMessage.getEventIdentification().getEventTypeCode().get(0); + assertEquals("ITI-48", eventTypeCode.getCode()); + assertEquals("IHE Transactions", eventTypeCode.getCodeSystemName()); + assertEquals("Retrieve Value Set", eventTypeCode.getOriginalText()); + assertEquals(2, auditMessage.getEventIdentification().getPurposesOfUse().size()); + + final var human = auditMessage.getActiveParticipants().stream() + .filter(apt -> apt.getUserName() != null) + .findFirst().orElseThrow(() -> new AssertionError("Expected human active participant")); + assertFalse(human.isUserIsRequestor()); + assertEquals(USER_ID, human.getUserID()); + assertEquals(USER_NAME, human.getUserName()); + assertEquals(2, human.getRoleIDCodes().size()); + // ? + + assertEquals(auditContext.getAuditSourceId(), auditMessage.getAuditSourceIdentification().getAuditSourceID()); + assertEquals(auditContext.getAuditEnterpriseSiteId(), auditMessage.getAuditSourceIdentification().getAuditEnterpriseSiteID()); + + final var objectIdentification = auditMessage.getParticipantObjectIdentifications().get(0); + assertEquals(VALUE_SET_ID, objectIdentification.getParticipantObjectID()); + assertEquals(ParticipantObjectTypeCode.System, objectIdentification.getParticipantObjectTypeCode()); + assertEquals(ParticipantObjectTypeCodeRole.Report, objectIdentification.getParticipantObjectTypeCodeRole()); + assertEquals("10", objectIdentification.getParticipantObjectIDTypeCode().getCode()); + assertEquals("RFC-3881", objectIdentification.getParticipantObjectIDTypeCode().getCodeSystemName()); + assertEquals("Search Criteria", objectIdentification.getParticipantObjectIDTypeCode().getOriginalText()); + assertEquals(VALUE_SET_NAME, objectIdentification.getParticipantObjectName()); + assertArrayEquals(VALUE_SET_VERSION.getBytes(StandardCharsets.UTF_8), objectIdentification.getParticipantObjectDetails().get(0).getValue()); + } +} \ No newline at end of file diff --git a/dependencies/pom.xml b/dependencies/pom.xml index b44466610c..45177eeed0 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -581,6 +581,11 @@ ipf-commons-ihe-hl7v3model ${project.version} + + org.openehealth.ipf.commons + ipf-commons-ihe-svs + ${project.version} + org.openehealth.ipf.commons ipf-commons-ihe-ws @@ -728,6 +733,11 @@ ipf-platform-camel-ihe-mllp ${project.version} + + org.openehealth.ipf.platform-camel + ipf-platform-camel-ihe-svs + ${project.version} + org.openehealth.ipf.platform-camel ipf-platform-camel-ihe-ws diff --git a/platform-camel/ihe/pom.xml b/platform-camel/ihe/pom.xml index ffaf1c6fc8..1ef742b0c6 100644 --- a/platform-camel/ihe/pom.xml +++ b/platform-camel/ihe/pom.xml @@ -37,6 +37,7 @@ core hpd xacml20 + svs diff --git a/platform-camel/ihe/svs/pom.xml b/platform-camel/ihe/svs/pom.xml new file mode 100644 index 0000000000..6b3ac14508 --- /dev/null +++ b/platform-camel/ihe/svs/pom.xml @@ -0,0 +1,132 @@ + + 4.0.0 + ipf-platform-camel-ihe-svs + ipf-platform-camel-ihe-svs + IHE SVS components for Apache Camel + + + org.openehealth.ipf.platform-camel + ipf-platform-camel-ihe + 5.0-SNAPSHOT + + + + + + org.openehealth.ipf.platform-camel + ipf-platform-camel-ihe-ws + ${project.version} + + + org.openehealth.ipf.commons + ipf-commons-ihe-svs + ${project.version} + + + org.openehealth.ipf.platform-camel + ipf-platform-camel-core + ${project.version} + + + + + org.openehealth.ipf.commons + ipf-commons-ihe-ws + ${project.version} + test + test-jar + + + org.openehealth.ipf.commons + ipf-commons-spring + ${project.version} + test + + + org.openehealth.ipf.platform-camel + ipf-platform-camel-ihe-ws + ${project.version} + test + test-jar + + + org.springframework + spring-web + test + + + org.apache.camel + camel-http + test + + + org.apache.camel + camel-groovy + test + + + org.apache.cxf.services.sts + cxf-services-sts-core + test + + + org.eclipse.jetty + jetty-http + test + + + org.eclipse.jetty + jetty-util + test + + + org.eclipse.jetty + jetty-io + test + + + org.eclipse.jetty.ee10 + jetty-ee10-servlet + test + + + org.apache.camel + camel-cxf-spring-soap + test + + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + maven-surefire-plugin + + false + -Xms64m -Xmx1024m + + + + + + + \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/core/SvsCamelValidators.java b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/core/SvsCamelValidators.java new file mode 100644 index 0000000000..365bc1949c --- /dev/null +++ b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/core/SvsCamelValidators.java @@ -0,0 +1,52 @@ +/* + * Copyright 2025 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.svs.core; + +import org.apache.camel.Processor; +import org.openehealth.ipf.commons.ihe.svs.core.SvsValidator; +import org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest; +import org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse; + +import static org.openehealth.ipf.platform.camel.core.adapter.ValidatorAdapter.validationEnabled; + +/** + * @author Quentin Ligier + **/ +public class SvsCamelValidators { + + private static final Processor ITI_48_REQUEST_VALIDATOR = exchange -> { + if (validationEnabled(exchange)) { + var request = exchange.getIn().getMandatoryBody(RetrieveValueSetRequest.class); + SvsValidator.validateIti48Request(request); + } + }; + + private static final Processor ITI_48_RESPONSE_VALIDATOR = exchange -> { + if (validationEnabled(exchange)) { + var response = exchange.getIn().getMandatoryBody(RetrieveValueSetResponse.class); + SvsValidator.validateIti48Response(response); + } + }; + + public static Processor iti48RequestValidator() { + return ITI_48_REQUEST_VALIDATOR; + } + + public static Processor iti48ResponseValidator() { + return ITI_48_RESPONSE_VALIDATOR; + } +} diff --git a/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/core/converters/SvsConverters.java b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/core/converters/SvsConverters.java new file mode 100644 index 0000000000..da9cf4dc5d --- /dev/null +++ b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/core/converters/SvsConverters.java @@ -0,0 +1,88 @@ +/* + * Copyright 2025 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.svs.core.converters; + +import org.apache.camel.Converter; +import org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest; +import org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse; + +import jakarta.xml.bind.JAXBContext; +import jakarta.xml.bind.JAXBException; +import jakarta.xml.bind.Marshaller; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +/** + * Camel type converters for SVS models. + * + * @author Quentin Ligier + */ +@Converter(generateLoader = true) +public class SvsConverters { + + private static final JAXBContext JAXB_CONTEXT_SVS_REQUEST; + private static final JAXBContext JAXB_CONTEXT_SVS_RESPONSE; + static { + try { + JAXB_CONTEXT_SVS_REQUEST = JAXBContext.newInstance(RetrieveValueSetRequest.class); + JAXB_CONTEXT_SVS_RESPONSE = JAXBContext.newInstance(RetrieveValueSetResponse.class); + } catch (final JAXBException e) { + throw new ExceptionInInitializerError(e); + } + } + + @Converter + public static RetrieveValueSetRequest xmlToSvsQuery(final String xml) throws JAXBException { + var unmarshaller = JAXB_CONTEXT_SVS_REQUEST.createUnmarshaller(); + return (RetrieveValueSetRequest) unmarshaller.unmarshal(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); + } + + @Converter + public static String svsQueryToXml(final RetrieveValueSetRequest query) throws JAXBException { + var marshaller = JAXB_CONTEXT_SVS_REQUEST.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); + marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF8"); + var stringWriter = new StringWriter(); + marshaller.marshal(query, stringWriter); + return stringWriter.toString(); + } + + @Converter + public static InputStream svsQueryToInputStream(final RetrieveValueSetRequest query) throws JAXBException { + return new ByteArrayInputStream(svsQueryToXml(query).getBytes(StandardCharsets.UTF_8)); + } + + @Converter + public static RetrieveValueSetResponse xmlToSvsResponse(final String xml) throws JAXBException { + var unmarshaller = JAXB_CONTEXT_SVS_RESPONSE.createUnmarshaller(); + return (RetrieveValueSetResponse) unmarshaller.unmarshal(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))); + } + + @Converter + public static String svsResponseToXml(final RetrieveValueSetResponse response) throws JAXBException { + var marshaller = JAXB_CONTEXT_SVS_RESPONSE.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE); + marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE); + marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF8"); + var stringWriter = new StringWriter(); + marshaller.marshal(response, stringWriter); + return stringWriter.toString(); + } +} \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/core/converters/SvsConvertersLoader.java b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/core/converters/SvsConvertersLoader.java new file mode 100644 index 0000000000..e525b99f52 --- /dev/null +++ b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/core/converters/SvsConvertersLoader.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 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.svs.core.converters; + +import org.apache.camel.TypeConverterLoaderException; +import org.apache.camel.spi.TypeConverterLoader; +import org.apache.camel.spi.TypeConverterRegistry; +import org.apache.camel.support.SimpleTypeConverter; + +/** + * Generated by camel build tools - do NOT edit this file! + */ +public final class SvsConvertersLoader implements TypeConverterLoader { + + public SvsConvertersLoader() { + } + + @Override + public void load(TypeConverterRegistry registry) throws TypeConverterLoaderException { + registerConverters(registry); + } + + private void registerConverters(TypeConverterRegistry registry) { + addTypeConverter(registry, java.lang.String.class, org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest.class, false, + (type, exchange, value) -> org.openehealth.ipf.platform.camel.ihe.svs.core.converters.SvsConverters.svsQueryToXml((org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest) value)); + addTypeConverter(registry, java.io.InputStream.class, org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest.class, false, + (type, exchange, value) -> org.openehealth.ipf.platform.camel.ihe.svs.core.converters.SvsConverters.svsQueryToInputStream((org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest) value)); + addTypeConverter(registry, java.lang.String.class, org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse.class, false, + (type, exchange, value) -> org.openehealth.ipf.platform.camel.ihe.svs.core.converters.SvsConverters.svsResponseToXml((org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse) value)); + addTypeConverter(registry, org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest.class, java.lang.String.class, false, + (type, exchange, value) -> org.openehealth.ipf.platform.camel.ihe.svs.core.converters.SvsConverters.xmlToSvsQuery((java.lang.String) value)); + addTypeConverter(registry, org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse.class, java.lang.String.class, false, + (type, exchange, value) -> org.openehealth.ipf.platform.camel.ihe.svs.core.converters.SvsConverters.xmlToSvsResponse((java.lang.String) value)); + } + + private static void addTypeConverter(TypeConverterRegistry registry, Class toType, Class fromType, boolean allowNull, SimpleTypeConverter.ConversionMethod method) { + registry.addTypeConverter(toType, fromType, new SimpleTypeConverter(allowNull, method)); + } + +} \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/Iti48Component.java b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/Iti48Component.java new file mode 100644 index 0000000000..237ad2d864 --- /dev/null +++ b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/Iti48Component.java @@ -0,0 +1,82 @@ +/* + * Copyright 2025 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.svs.iti48; + +import org.apache.camel.Endpoint; +import org.openehealth.ipf.commons.ihe.svs.core.audit.SvsAuditDataset; +import org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest; +import org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse; +import org.openehealth.ipf.commons.ihe.ws.*; +import org.openehealth.ipf.platform.camel.ihe.ws.AbstractWsComponent; +import org.openehealth.ipf.platform.camel.ihe.ws.AbstractWsEndpoint; +import org.openehealth.ipf.platform.camel.ihe.ws.AbstractWsProducer; +import org.openehealth.ipf.platform.camel.ihe.ws.SimpleWsProducer; + +import java.util.Map; + +import static org.openehealth.ipf.commons.ihe.svs.SVS.Interactions.ITI_48; + +/** + * The Camel component for the ITI-48 transaction. + * + * @author Quentin Ligier + */ +public class Iti48Component extends AbstractWsComponent, WsInteractionId>> { + + public Iti48Component() { + super(ITI_48); + } + + @Override + protected Endpoint createEndpoint(String uri, String remaining, Map parameters) { + return new AbstractWsEndpoint<>(uri, remaining, this, parameters, Iti48Service.class) { + + @Override + public JaxWsClientFactory getJaxWsClientFactory() { + return new JaxWsRequestClientFactory<>( + getComponent().getWsTransactionConfiguration(), + getServiceUrl(), + isAudit() ? getClientAuditStrategy() : null, + getAuditContext(), + getCustomCxfInterceptors(), + getFeatures(), + getProperties(), + getCorrelator(), + getSecurityInformation(), + getHttpClientPolicy()); + } + + @Override + public JaxWsServiceFactory getJaxWsServiceFactory() { + return new JaxWsRequestServiceFactory<>( + getComponent().getWsTransactionConfiguration(), + getServiceAddress(), + isAudit() ? getComponent().getServerAuditStrategy() : null, + getAuditContext(), + getCustomCxfInterceptors(), + getRejectionHandlingStrategy()); + } + + @Override + public AbstractWsProducer, RetrieveValueSetRequest, RetrieveValueSetResponse> getProducer(AbstractWsEndpoint> endpoint, JaxWsClientFactory clientFactory) { + return new SimpleWsProducer<>( + endpoint, clientFactory, RetrieveValueSetRequest.class, RetrieveValueSetResponse.class + ); + } + }; + } +} \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/Iti48Service.java b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/Iti48Service.java new file mode 100644 index 0000000000..7a12b8a584 --- /dev/null +++ b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/Iti48Service.java @@ -0,0 +1,59 @@ +/* + * Copyright 2025 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.svs.iti48; + +import lombok.extern.slf4j.Slf4j; +import org.apache.camel.Exchange; +import org.apache.cxf.binding.soap.Soap12; +import org.apache.cxf.binding.soap.SoapFault; +import org.openehealth.ipf.commons.ihe.svs.core.SvsException; +import org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest; +import org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse; +import org.openehealth.ipf.commons.ihe.svs.iti48.Iti48PortType; +import org.openehealth.ipf.platform.camel.core.util.Exchanges; +import org.openehealth.ipf.platform.camel.ihe.ws.AbstractWebService; + +/** + * Service implementation for the IHE ITI-48 transaction (Retrieve Value Set). + *

+ * This implementation delegates to a Camel consumer by creating an exchange. + * + * @author Quentin Ligier + */ +@Slf4j +public class Iti48Service extends AbstractWebService implements Iti48PortType { + + @Override + public RetrieveValueSetResponse valueSetRepositoryRetrieveValueSet(final RetrieveValueSetRequest body) { + final Exchange result = this.process(body); + var exception = Exchanges.extractException(result); + if (exception != null) { + log.debug("Iti-48 service failed", exception); + if (exception instanceof SoapFault) { + // Pass it through + throw (SoapFault) exception; + } else if (exception instanceof SvsException) { + // Most probably thrown from the validator + throw new SoapFault(exception.getMessage(), Soap12.getInstance().getSender()); + } else { + // Wrap it in a SoapFault + throw new SoapFault("An error occurred", Soap12.getInstance().getSender()); + } + } + return result.getMessage().getBody(RetrieveValueSetResponse.class); + } +} \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/exceptions/ChUnknownLanguageException.java b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/exceptions/ChUnknownLanguageException.java new file mode 100644 index 0000000000..41a06d350d --- /dev/null +++ b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/exceptions/ChUnknownLanguageException.java @@ -0,0 +1,39 @@ +/* + * Copyright 2025 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.svs.iti48.exceptions; + +import org.apache.cxf.binding.soap.Soap12; +import org.apache.cxf.binding.soap.SoapFault; + +import javax.xml.namespace.QName; + +/** + * A SOAP exception for an unknown value set language. Defined in the Swiss "EPR – Central Services Interface + * Documentation" document. + * + * @author Quentin Ligier + */ +public class ChUnknownLanguageException extends SoapFault { + + public ChUnknownLanguageException(final String language) { + super( + String.format("Language '%s' not supported", language), + Soap12.getInstance().getSender() + ); + this.setSubCode(new QName(null, "LANGUNK")); + } +} \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/exceptions/UnknownValueSetException.java b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/exceptions/UnknownValueSetException.java new file mode 100644 index 0000000000..267475a860 --- /dev/null +++ b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/exceptions/UnknownValueSetException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 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.svs.iti48.exceptions; + +import org.apache.cxf.binding.soap.Soap12; +import org.apache.cxf.binding.soap.SoapFault; + +import javax.xml.namespace.QName; + +/** + * A SOAP exception for an unknown value set identifier. Defined by IHE. + * + * @author Quentin Ligier + */ +public class UnknownValueSetException extends SoapFault { + + public UnknownValueSetException() { + super("Unknown value set", Soap12.getInstance().getSender()); + this.setSubCode(new QName(null, "NAV")); + } +} \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/exceptions/UnknownVersionException.java b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/exceptions/UnknownVersionException.java new file mode 100644 index 0000000000..d1a7e5c540 --- /dev/null +++ b/platform-camel/ihe/svs/src/main/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/exceptions/UnknownVersionException.java @@ -0,0 +1,35 @@ +/* + * Copyright 2025 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.svs.iti48.exceptions; + +import org.apache.cxf.binding.soap.Soap12; +import org.apache.cxf.binding.soap.SoapFault; + +import javax.xml.namespace.QName; + +/** + * A SOAP exception for an unknown value set version. Defined by IHE. + * + * @author Quentin Ligier + */ +public class UnknownVersionException extends SoapFault { + + public UnknownVersionException() { + super("Version unknown", Soap12.getInstance().getSender()); + this.setSubCode(new QName(null, "VERUNK")); + } +} \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/main/resources/META-INF/LICENSE.txt b/platform-camel/ihe/svs/src/main/resources/META-INF/LICENSE.txt new file mode 100644 index 0000000000..d645695673 --- /dev/null +++ b/platform-camel/ihe/svs/src/main/resources/META-INF/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/platform-camel/ihe/svs/src/main/resources/META-INF/NOTICE.txt b/platform-camel/ihe/svs/src/main/resources/META-INF/NOTICE.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/platform-camel/ihe/svs/src/main/resources/META-INF/services/org/apache/camel/TypeConverterLoader b/platform-camel/ihe/svs/src/main/resources/META-INF/services/org/apache/camel/TypeConverterLoader new file mode 100644 index 0000000000..be002eb2ab --- /dev/null +++ b/platform-camel/ihe/svs/src/main/resources/META-INF/services/org/apache/camel/TypeConverterLoader @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +org.openehealth.ipf.platform.camel.ihe.svs.core.converters.SvsConvertersLoader \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/main/resources/META-INF/services/org/apache/camel/component/svs-iti48 b/platform-camel/ihe/svs/src/main/resources/META-INF/services/org/apache/camel/component/svs-iti48 new file mode 100644 index 0000000000..613da47d09 --- /dev/null +++ b/platform-camel/ihe/svs/src/main/resources/META-INF/services/org/apache/camel/component/svs-iti48 @@ -0,0 +1,12 @@ +# Copyright 2025 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. + +# Camel registration for the svs-iti48 component + +class=org.openehealth.ipf.platform.camel.ihe.svs.iti48.Iti48Component \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/test/groovy/org/openehealth/ipf/platform/camel/ihe/svs/iti48/TestIti48.groovy b/platform-camel/ihe/svs/src/test/groovy/org/openehealth/ipf/platform/camel/ihe/svs/iti48/TestIti48.groovy new file mode 100644 index 0000000000..59d23a2eec --- /dev/null +++ b/platform-camel/ihe/svs/src/test/groovy/org/openehealth/ipf/platform/camel/ihe/svs/iti48/TestIti48.groovy @@ -0,0 +1,83 @@ +package org.openehealth.ipf.platform.camel.ihe.svs.iti48 + +import org.apache.cxf.binding.soap.SoapFault +import org.apache.cxf.transport.servlet.CXFServlet +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest +import org.openehealth.ipf.commons.ihe.svs.core.requests.ValueSetRequest +import org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse +import org.openehealth.ipf.platform.camel.ihe.svs.core.converters.SvsConverters +import org.openehealth.ipf.platform.camel.ihe.ws.StandardTestContainer + +import static org.junit.jupiter.api.Assertions.* + +/** + * @author Quentin Ligier + * */ +class TestIti48 extends StandardTestContainer { + + static final String CONTEXT_DESCRIPTOR = 'iti-48.xml' + + final String SERVICE1 = "svs-iti48://localhost:${port}/service1-ok" + final String SERVICE2 = "svs-iti48://localhost:${port}/service2-exception" + + static void main(args) { + startServer(new CXFServlet(), CONTEXT_DESCRIPTOR, false, DEMO_APP_PORT) + } + + @BeforeAll + static void classSetUp() { + startServer(new CXFServlet(), CONTEXT_DESCRIPTOR) + } + + @Test + void testIti48() { + assert auditSender.messages.size() == 0 + + def request = SvsConverters.xmlToSvsQuery(""" + + + + """) + + def response = sendIt(SERVICE1, request) + assert response != null + assert response.valueSet != null + assert response.valueSet.id == "1.2.840.10008.6.1.308" + assert response.valueSet.conceptList.size() == 1 + assert response.valueSet.conceptList[0].concept.size() == 2 + + assert auditSender.messages.size() == 2 + + SoapFault exception = assertThrows(SoapFault.class, () -> { + sendIt(SERVICE2, request) + }) + assert exception != null + assert exception.message == "Language 'en-EN' not supported" + assert exception.getFaultCode().localPart == "Sender" + assert exception.subCode.localPart == "LANGUNK" + + assert auditSender.messages.size() == 4 + for (message in auditSender.messages) { + assert message.eventIdentification.eventTypeCode[0].code == "ITI-48" + assert message.eventIdentification.eventTypeCode[0].codeSystemName == "IHE Transactions" + assert message.eventIdentification.eventTypeCode[0].originalText == "Retrieve Value Set" + + assert message.participantObjectIdentifications[0].participantObjectID == "1.2.840.10008.6.1.308" + } + + request.valueSet = new ValueSetRequest() + exception = assertThrows(SoapFault.class, () -> { + sendIt(SERVICE2, request) + }) + assert exception != null + assert exception.getFaultCode().localPart == "Sender" + + assert auditSender.messages.size() == 6 + } + + RetrieveValueSetResponse sendIt(String endpoint, RetrieveValueSetRequest request) { + return send(endpoint, request, RetrieveValueSetResponse.class) + } +} diff --git a/platform-camel/ihe/svs/src/test/java/org/openehealth/ipf/platform/camel/ihe/svs/core/converters/SvsConvertersTest.java b/platform-camel/ihe/svs/src/test/java/org/openehealth/ipf/platform/camel/ihe/svs/core/converters/SvsConvertersTest.java new file mode 100644 index 0000000000..dc9fdc4fa6 --- /dev/null +++ b/platform-camel/ihe/svs/src/test/java/org/openehealth/ipf/platform/camel/ihe/svs/core/converters/SvsConvertersTest.java @@ -0,0 +1,129 @@ +/* + * Copyright 2025 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.svs.core.converters; + +import jakarta.xml.bind.JAXBException; +import org.junit.jupiter.api.Test; +import org.openehealth.ipf.commons.ihe.svs.core.requests.RetrieveValueSetRequest; +import org.openehealth.ipf.commons.ihe.svs.core.requests.ValueSetRequest; +import org.openehealth.ipf.commons.ihe.svs.core.responses.Concept; +import org.openehealth.ipf.commons.ihe.svs.core.responses.ConceptList; +import org.openehealth.ipf.commons.ihe.svs.core.responses.RetrieveValueSetResponse; +import org.openehealth.ipf.commons.ihe.svs.core.responses.ValueSetResponse; + +import java.time.OffsetDateTime; + +import static org.junit.jupiter.api.Assertions.*; + + +/** + * @author Quentin Ligier + **/ +class SvsConvertersTest { + + @Test + public void testMarshalUnmarshalRequest() throws JAXBException { + var expectedRequest = new RetrieveValueSetRequest(new ValueSetRequest("1.2.3", "en", "3")); + var xml = SvsConverters.svsQueryToXml(expectedRequest); + var request = SvsConverters.xmlToSvsQuery(xml); + + assertEquals(expectedRequest.getValueSet().getId(), request.getValueSet().getId()); + assertEquals(expectedRequest.getValueSet().getLang(), request.getValueSet().getLang()); + assertEquals(expectedRequest.getValueSet().getVersion(), request.getValueSet().getVersion()); + } + + @Test + public void testUnmarshalRequest() throws JAXBException { + var xml = """ + + + + """; + var request = SvsConverters.xmlToSvsQuery(xml); + + assertEquals("1.2.840.10008.6.1.308", request.getValueSet().getId()); + assertEquals("en-EN", request.getValueSet().getLang()); + assertNull(request.getValueSet().getVersion()); + } + + @Test + public void testMarshalUnmarshalResponse() throws JAXBException { + var valueSet = new ValueSetResponse("1.2.3", "Value Set", "3"); + var conceptList = new ConceptList("en"); + conceptList.getConcept().add(new Concept("10.1", "1.2.3", "Code", "MyCodeSystem", "3")); + valueSet.getConceptList().add(conceptList); + var expectedResponse = new RetrieveValueSetResponse(valueSet, OffsetDateTime.now()); + + var xml = SvsConverters.svsResponseToXml(expectedResponse); + var response = SvsConverters.xmlToSvsResponse(xml); + + assertNotNull(response); + assertEquals(expectedResponse.getValueSet().getId(), response.getValueSet().getId()); + assertEquals(expectedResponse.getValueSet().getDisplayName(), response.getValueSet().getDisplayName()); + assertEquals(expectedResponse.getValueSet().getVersion(), response.getValueSet().getVersion()); + assertEquals(expectedResponse.getValueSet().getConceptList().get(0).getLang(), + response.getValueSet().getConceptList().get(0).getLang()); + assertEquals(expectedResponse.getValueSet().getConceptList().get(0).getConcept().get(0).getCode(), + response.getValueSet().getConceptList().get(0).getConcept().get(0).getCode()); + assertEquals(expectedResponse.getValueSet().getConceptList().get(0).getConcept().get(0).getDisplayName(), + response.getValueSet().getConceptList().get(0).getConcept().get(0).getDisplayName()); + assertEquals(expectedResponse.getValueSet().getConceptList().get(0).getConcept().get(0).getCodeSystem(), + response.getValueSet().getConceptList().get(0).getConcept().get(0).getCodeSystem()); + assertEquals(expectedResponse.getValueSet().getConceptList().get(0).getConcept().get(0).getCodeSystemName(), + response.getValueSet().getConceptList().get(0).getConcept().get(0).getCodeSystemName()); + assertEquals(expectedResponse.getValueSet().getConceptList().get(0).getConcept().get(0).getCodeSystemVersion(), + response.getValueSet().getConceptList().get(0).getConcept().get(0).getCodeSystemVersion()); + } + + @Test + public void testUnmarshalResponse() throws JAXBException { + var xml = """ + + + + + + + + + """; + var response = SvsConverters.xmlToSvsResponse(xml); + + assertEquals(1218776400, response.getCacheExpirationHint().toEpochSecond()); + assertEquals("1.2.840.10008.6.1.308", response.getValueSet().getId()); + assertEquals("Common Anatomic Regions Context ID 4031", response.getValueSet().getDisplayName()); + assertEquals("20061023", response.getValueSet().getVersion()); + + assertEquals(1, response.getValueSet().getConceptList().size()); + var conceptList = response.getValueSet().getConceptList().get(0); + assertEquals("en-US", conceptList.getLang()); + + assertEquals(2, conceptList.getConcept().size()); + + assertEquals("T-D4000", conceptList.getConcept().get(0).getCode()); + assertEquals("Abdomen", conceptList.getConcept().get(0).getDisplayName()); + assertEquals("2.16.840.1.113883.6.5", conceptList.getConcept().get(0).getCodeSystem()); + assertNull(conceptList.getConcept().get(0).getCodeSystemName()); + assertNull(conceptList.getConcept().get(0).getCodeSystemVersion()); + + assertEquals("R-FAB57", conceptList.getConcept().get(1).getCode()); + assertEquals("Abdomen and Pelvis", conceptList.getConcept().get(1).getDisplayName()); + assertEquals("2.16.840.1.113883.6.5", conceptList.getConcept().get(1).getCodeSystem()); + assertNull(conceptList.getConcept().get(1).getCodeSystemName()); + assertNull(conceptList.getConcept().get(1).getCodeSystemVersion()); + } +} \ No newline at end of file diff --git a/platform-camel/ihe/svs/src/test/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/Iti48TestRouteBuilder.java b/platform-camel/ihe/svs/src/test/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/Iti48TestRouteBuilder.java new file mode 100644 index 0000000000..674b31b7a7 --- /dev/null +++ b/platform-camel/ihe/svs/src/test/java/org/openehealth/ipf/platform/camel/ihe/svs/iti48/Iti48TestRouteBuilder.java @@ -0,0 +1,55 @@ +/* + * Copyright 2025 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.svs.iti48; + +import org.apache.camel.builder.RouteBuilder; +import org.openehealth.ipf.platform.camel.ihe.svs.core.SvsCamelValidators; +import org.openehealth.ipf.platform.camel.ihe.svs.core.converters.SvsConverters; +import org.openehealth.ipf.platform.camel.ihe.svs.iti48.exceptions.ChUnknownLanguageException; + +/** + * @author Quentin Ligier + **/ +public class Iti48TestRouteBuilder extends RouteBuilder { + + @Override + public void configure() throws Exception { + from("svs-iti48:service1-ok") + .process(SvsCamelValidators.iti48RequestValidator()) + .process(exchange -> { + var response = SvsConverters.xmlToSvsResponse(""" + + + + + + + + + """); + exchange.getMessage().setBody(response); + }) + .process(SvsCamelValidators.iti48ResponseValidator()); + + from("svs-iti48:service2-exception") + .process(SvsCamelValidators.iti48RequestValidator()) + .process(exchange -> { + throw new ChUnknownLanguageException("en-EN"); + }) + .process(SvsCamelValidators.iti48ResponseValidator()); + } +} diff --git a/platform-camel/ihe/svs/src/test/resources/iti-48.xml b/platform-camel/ihe/svs/src/test/resources/iti-48.xml new file mode 100644 index 0000000000..d978418873 --- /dev/null +++ b/platform-camel/ihe/svs/src/test/resources/iti-48.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/platform-camel/ihe/svs/src/test/resources/log4j2.xml b/platform-camel/ihe/svs/src/test/resources/log4j2.xml new file mode 100644 index 0000000000..fd33e89f7f --- /dev/null +++ b/platform-camel/ihe/svs/src/test/resources/log4j2.xml @@ -0,0 +1,21 @@ + + + + + + %d{ABSOLUTE} [%t] %-5p - %C{1}.%M(%L) | %m%n + + + + + + + + + + + + + + + diff --git a/pom.xml b/pom.xml index 5613c6f52d..d4104ba80c 100644 --- a/pom.xml +++ b/pom.xml @@ -406,6 +406,7 @@ commons/ihe/hl7v3/generated-stubs: commons/ihe/hl7v3model/generated-stubs: commons/ihe/hpd/generated-stubs: + commons/ihe/svs/generated-stubs: commons/ihe/ws/generated-stubs: commons/ihe/xacml20/impl/generated-stubs: commons/ihe/xacml20/model/generated-stubs: @@ -433,6 +434,7 @@ platform-camel/ihe/hl7v3/generated-stubs: platform-camel/ihe/hpd/generated-stubs: platform-camel/ihe/mllp/generated-stubs: + platform-camel/ihe/svs/generated-stubs: platform-camel/ihe/ws/generated-stubs: platform-camel/ihe/xacml20/generated-stubs: platform-camel/ihe/xds/generated-stubs: