From 07f4419444885da2b3493708055be6c2d82ff631 Mon Sep 17 00:00:00 2001
From: Dmytro Rud <Dmytro.Rud@adesso.ch>
Date: Tue, 31 Oct 2023 20:46:08 +0100
Subject: [PATCH] #431: better error handling for PPQ-2

---
 .../translation/XacmlToFhirTranslator.groovy  | 15 +++---
 .../ihe/fhir/chppqm/TranslationTest.java      |  6 +--
 .../ihe/xacml20/ChPpqMessageCreator.groovy    | 35 +++++++------
 .../commons/ihe/xacml20/Xacml20Exception.java | 45 ++++++++++++++++
 .../commons/ihe/xacml20/Xacml20Status.java    | 34 +++++++++++++
 .../ipf/commons/ihe/xacml20/Xacml20Utils.java | 20 +-------
 .../xacml20/chppq2/ChPpq2AuditStrategy.java   |  3 +-
 .../chppq2/ppq-query-backend-response.xml     |  2 +-
 .../ihe/xacml20/chppq2/ChPpq2Component.java   | 14 +----
 .../ihe/xacml20/chppq2/ChPpq2Endpoint.java    | 51 +++++++++++++++++++
 .../ihe/xacml20/chppq2/ChPpq2Service.java     | 12 +++--
 .../camel/ihe/xacml20/chppq2/ChPpq2Test.java  | 20 +++++---
 .../chppq2/ChPpq2TestRouteBuilder.java        |  4 +-
 src/site/changes.xml                          |  6 +++
 14 files changed, 192 insertions(+), 75 deletions(-)
 create mode 100644 commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Exception.java
 create mode 100644 commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Status.java
 create mode 100644 platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Endpoint.java

diff --git a/commons/ihe/fhir/r4/chppqm/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/chppqm/translation/XacmlToFhirTranslator.groovy b/commons/ihe/fhir/r4/chppqm/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/chppqm/translation/XacmlToFhirTranslator.groovy
index 843f215353..e6745a2503 100644
--- a/commons/ihe/fhir/r4/chppqm/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/chppqm/translation/XacmlToFhirTranslator.groovy
+++ b/commons/ihe/fhir/r4/chppqm/src/main/groovy/org/openehealth/ipf/commons/ihe/fhir/chppqm/translation/XacmlToFhirTranslator.groovy
@@ -31,6 +31,7 @@ import org.hl7.fhir.r4.model.Consent
 import org.hl7.fhir.r4.model.IdType
 import org.hl7.fhir.r4.model.OperationOutcome
 import org.openehealth.ipf.commons.ihe.fhir.chppqm.ChPpqmUtils
+import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Status
 import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Utils
 import org.openehealth.ipf.commons.ihe.xacml20.model.PpqConstants
 import org.openehealth.ipf.commons.ihe.xacml20.stub.UnknownPolicySetIdFaultMessage
@@ -254,15 +255,15 @@ class XacmlToFhirTranslator {
     }
 
     static final Map<String, OperationOutcome.IssueType> SAML_STATUS_CODE_TO_FHIR_ISSUE_TYPE_CODE_MAPPING = [
-            'urn:oasis:names:tc:SAML:2.0:status:Requester'      : OperationOutcome.IssueType.INVALID,
-            'urn:oasis:names:tc:SAML:2.0:status:Responder'      : OperationOutcome.IssueType.INVALID,
-            'urn:oasis:names:tc:SAML:2.0:status:VersionMismatch': OperationOutcome.IssueType.STRUCTURE,
+            (Xacml20Status.REQUESTER_ERROR.code) : OperationOutcome.IssueType.INVALID,
+            (Xacml20Status.RESPONDER_ERROR.code) : OperationOutcome.IssueType.INVALID,
+            (Xacml20Status.VERSION_MISMATCH.code): OperationOutcome.IssueType.STRUCTURE,
     ]
 
     static final Map<String, Integer> SAML_STATUS_CODE_TO_HTTP_STATUS_CODE_MAPPING = [
-            'urn:oasis:names:tc:SAML:2.0:status:Requester'      : 400,
-            'urn:oasis:names:tc:SAML:2.0:status:Responder'      : 500,
-            'urn:oasis:names:tc:SAML:2.0:status:VersionMismatch': 500,
+            (Xacml20Status.REQUESTER_ERROR.code) : 400,
+            (Xacml20Status.RESPONDER_ERROR.code) : 500,
+            (Xacml20Status.VERSION_MISMATCH.code): 500,
     ]
 
     /**
@@ -270,7 +271,7 @@ class XacmlToFhirTranslator {
      */
     static List<Consent> translatePpq2To5Response(ResponseType ppq2Response) {
         def statusCode = ppq2Response.status.statusCode.value
-        if (statusCode == Xacml20Utils.SAML20_STATUS_SUCCESS) {
+        if (statusCode == Xacml20Status.SUCCESS.code) {
             def assertion = ppq2Response.assertionOrEncryptedAssertion[0] as AssertionType
             def statement = assertion.statementOrAuthnStatementOrAuthzDecisionStatement[0] as XACMLPolicyStatementType
             return statement.policyOrPolicySet.collect { toConsent(it as PolicySetType) }
diff --git a/commons/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/commons/ihe/fhir/chppqm/TranslationTest.java b/commons/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/commons/ihe/fhir/chppqm/TranslationTest.java
index f2ef58051d..433e5c5bc2 100644
--- a/commons/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/commons/ihe/fhir/chppqm/TranslationTest.java
+++ b/commons/ihe/fhir/r4/chppqm/src/test/java/org/openehealth/ipf/commons/ihe/fhir/chppqm/TranslationTest.java
@@ -35,9 +35,7 @@
 import org.openehealth.ipf.commons.ihe.fhir.chppqm.chppq4.ChPpq4Validator;
 import org.openehealth.ipf.commons.ihe.fhir.chppqm.translation.FhirToXacmlTranslator;
 import org.openehealth.ipf.commons.ihe.fhir.chppqm.translation.XacmlToFhirTranslator;
-import org.openehealth.ipf.commons.ihe.xacml20.ChPpqMessageCreator;
-import org.openehealth.ipf.commons.ihe.xacml20.Xacml20MessageValidator;
-import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Utils;
+import org.openehealth.ipf.commons.ihe.xacml20.*;
 import org.openehealth.ipf.commons.ihe.xacml20.model.PpqConstants;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.AddPolicyRequest;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.DeletePolicyRequest;
@@ -259,7 +257,7 @@ public void testPpq2To5ResponseTranslation1() {
     public void testPpq2To5ResponseTranslation2() {
         boolean correct = false;
         try {
-            ResponseType ppq2Response = PPQ_MESSAGE_CREATOR.createNegativePolicyQueryResponse("urn:oasis:names:tc:SAML:2.0:status:Requester");
+            ResponseType ppq2Response = PPQ_MESSAGE_CREATOR.createNegativePolicyQueryResponse(new Xacml20Exception(Xacml20Status.REQUESTER_ERROR));
             XacmlToFhirTranslator.translatePpq2To5Response(ppq2Response);
         } catch (UnclassifiedServerFailureException e) {
             assertEquals(400, e.getStatusCode());
diff --git a/commons/ihe/xacml20/impl/src/main/groovy/org/openehealth/ipf/commons/ihe/xacml20/ChPpqMessageCreator.groovy b/commons/ihe/xacml20/impl/src/main/groovy/org/openehealth/ipf/commons/ihe/xacml20/ChPpqMessageCreator.groovy
index 7d638f95a9..6657f63a24 100644
--- a/commons/ihe/xacml20/impl/src/main/groovy/org/openehealth/ipf/commons/ihe/xacml20/ChPpqMessageCreator.groovy
+++ b/commons/ihe/xacml20/impl/src/main/groovy/org/openehealth/ipf/commons/ihe/xacml20/ChPpqMessageCreator.groovy
@@ -53,7 +53,7 @@ class ChPpqMessageCreator {
     private final String homeCommunityId
 
     ChPpqMessageCreator(String homeCommunityId) {
-        this.homeCommunityId = Validate.notEmpty(homeCommunityId)
+        this.homeCommunityId = Validate.notEmpty(homeCommunityId as String, 'Home community ID shall be provided')
     }
 
     private AssertionType createAssertion() {
@@ -143,32 +143,35 @@ class ChPpqMessageCreator {
         return query
     }
 
-    ResponseType createPositivePolicyQueryResponse(List<PolicySetType> policySets) {
-        def assertion = createAssertion()
-        assertion.statementOrAuthnStatementOrAuthzDecisionStatement << new XACMLPolicyStatementType(
-                policyOrPolicySet: policySets,
-        )
+    private static ResponseType createResponse(Xacml20Status status, String statusMessage, AssertionType assertion) {
         return new ResponseType(
                 ID: '_' + UUID.randomUUID(),
                 issueInstant: XML_OBJECT_FACTORY.newXMLGregorianCalendar(new GregorianCalendar()),
                 version: '2.0',
                 status: new StatusType(
-                        statusCode: new StatusCodeType(value: Xacml20Utils.SAML20_STATUS_SUCCESS),
+                        statusCode: new StatusCodeType(value: status.code),
+                        statusMessage: statusMessage,
                 ),
                 assertionOrEncryptedAssertion: [assertion],
         )
     }
 
-    ResponseType createNegativePolicyQueryResponse(String statusCode) {
-        return new ResponseType(
-                ID: '_' + UUID.randomUUID(),
-                issueInstant: XML_OBJECT_FACTORY.newXMLGregorianCalendar(new GregorianCalendar()),
-                version: '2.0',
-                status: new StatusType(
-                        statusCode: new StatusCodeType(value: statusCode),
-                ),
-                assertionOrEncryptedAssertion: [createAssertion()],
+    ResponseType createPositivePolicyQueryResponse(List<PolicySetType> policySets) {
+        def assertion = createAssertion()
+        assertion.statementOrAuthnStatementOrAuthzDecisionStatement << new XACMLPolicyStatementType(
+                policyOrPolicySet: policySets,
         )
+        return createResponse(Xacml20Status.SUCCESS, null, assertion)
+    }
+
+    ResponseType createNegativePolicyQueryResponse(Xacml20Status status, String statusMessage) {
+        return createResponse(status, statusMessage, createAssertion())
+    }
+
+    ResponseType createNegativePolicyQueryResponse(Exception exception) {
+        return (exception instanceof Xacml20Exception)
+                ? createNegativePolicyQueryResponse(exception.status, exception.message)
+                : createNegativePolicyQueryResponse(Xacml20Status.RESPONDER_ERROR, exception.message)
     }
 
 }
diff --git a/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Exception.java b/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Exception.java
new file mode 100644
index 0000000000..26f410e9cf
--- /dev/null
+++ b/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Exception.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.openehealth.ipf.commons.ihe.xacml20;
+
+import lombok.Getter;
+
+import java.util.Objects;
+
+public class Xacml20Exception extends Exception {
+
+    @Getter
+    private final Xacml20Status status;
+
+    public Xacml20Exception(Xacml20Status status, String message, Throwable cause) {
+        super(message, cause);
+        this.status = Objects.requireNonNull(status, "Status code shall be provided");
+    }
+
+    public Xacml20Exception(Xacml20Status status) {
+        this(status, null, null);
+    }
+
+    public Xacml20Exception(Xacml20Status status, String message) {
+        this(status, message, null);
+    }
+
+    public Xacml20Exception(Xacml20Status status, Throwable cause) {
+        this(status, cause.getMessage(), cause);
+    }
+
+}
diff --git a/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Status.java b/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Status.java
new file mode 100644
index 0000000000..2a74adb295
--- /dev/null
+++ b/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Status.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.openehealth.ipf.commons.ihe.xacml20;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public enum Xacml20Status {
+
+    SUCCESS("urn:oasis:names:tc:SAML:2.0:status:Success"),
+    REQUESTER_ERROR("urn:oasis:names:tc:SAML:2.0:status:Requester"),
+    RESPONDER_ERROR("urn:oasis:names:tc:SAML:2.0:status:Responder"),
+    VERSION_MISMATCH("urn:oasis:names:tc:SAML:2.0:status:VersionMismatch"),
+    ;
+
+    @Getter
+    private final String code;
+
+}
diff --git a/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Utils.java b/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Utils.java
index e17e6b2382..4c191d89b6 100644
--- a/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Utils.java
+++ b/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/Xacml20Utils.java
@@ -24,16 +24,10 @@
 import org.openehealth.ipf.commons.ihe.xacml20.herasaf.Hl7v3DataTypesInitializer;
 import org.openehealth.ipf.commons.ihe.xacml20.herasaf.Hl7v3FunctionsInitializer;
 import org.openehealth.ipf.commons.ihe.xacml20.herasaf.types.IiDataTypeAttribute;
-import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.AddPolicyRequest;
-import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.AssertionBasedRequestType;
-import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.DeletePolicyRequest;
-import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.UpdatePolicyRequest;
-import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.XACMLPolicySetIdReferenceStatementType;
+import org.openehealth.ipf.commons.ihe.xacml20.stub.ehealthswiss.*;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.hl7v3.II;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.assertion.AssertionType;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.ResponseType;
-import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.StatusCodeType;
-import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.StatusType;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.assertion.XACMLPolicyStatementType;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.protocol.XACMLPolicyQueryType;
 
@@ -52,8 +46,6 @@
  */
 public class Xacml20Utils {
 
-    public static final String SAML20_STATUS_SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success";
-
     public static final String ATTRIBUTE_TYPE_PATIENT_ID = "urn:e-health-suisse:2015:epr-spid";
     public static final String ELEMENT_NAME_PATIENT_ID = "InstanceIdentifier";
 
@@ -99,16 +91,6 @@ public static void initializeHerasaf(Initializer... customInitializers) {
         InitializerExecutor.runInitializers();
     }
 
-    public static ResponseType createXacmlQueryResponse(String status) {
-        var statusCodeType = new StatusCodeType();
-        statusCodeType.setValue(status);
-        var statusType = new StatusType();
-        statusType.setStatusCode(statusCodeType);
-        var responseType = new ResponseType();
-        responseType.setStatus(statusType);
-        return responseType;
-    }
-
     /**
      * Creates a stream of all policies and policy sets contained in the given PPQ response object.
      *
diff --git a/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/chppq2/ChPpq2AuditStrategy.java b/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/chppq2/ChPpq2AuditStrategy.java
index 6f78b87570..e64feeb408 100644
--- a/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/chppq2/ChPpq2AuditStrategy.java
+++ b/commons/ihe/xacml20/impl/src/main/java/org/openehealth/ipf/commons/ihe/xacml20/chppq2/ChPpq2AuditStrategy.java
@@ -29,6 +29,7 @@
 import org.openehealth.ipf.commons.audit.types.ParticipantObjectIdType;
 import org.openehealth.ipf.commons.ihe.core.atna.AuditStrategySupport;
 import org.openehealth.ipf.commons.ihe.core.atna.event.QueryInformationBuilder;
+import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Status;
 import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Utils;
 import org.openehealth.ipf.commons.ihe.xacml20.audit.ChPpqAuditDataset;
 import org.openehealth.ipf.commons.ihe.xacml20.audit.codes.PpqEventTypeCodes;
@@ -86,7 +87,7 @@ public boolean enrichAuditDatasetFromResponse(ChPpqAuditDataset auditDataset, Ob
     public EventOutcomeIndicator getEventOutcomeIndicator(ChPpqAuditDataset auditDataset, Object responseObject) {
         var response = (ResponseType) responseObject;
         try {
-            if (!Xacml20Utils.SAML20_STATUS_SUCCESS.equals(response.getStatus().getStatusCode().getValue())) {
+            if (!Xacml20Status.SUCCESS.getCode().equals(response.getStatus().getStatusCode().getValue())) {
                 return EventOutcomeIndicator.SeriousFailure;
             }
             if (response.getAssertionOrEncryptedAssertion().isEmpty()) {
diff --git a/commons/ihe/xacml20/impl/src/test/resources/messages/chppq2/ppq-query-backend-response.xml b/commons/ihe/xacml20/impl/src/test/resources/messages/chppq2/ppq-query-backend-response.xml
index 66e7d78a78..6c6a7ddb87 100644
--- a/commons/ihe/xacml20/impl/src/test/resources/messages/chppq2/ppq-query-backend-response.xml
+++ b/commons/ihe/xacml20/impl/src/test/resources/messages/chppq2/ppq-query-backend-response.xml
@@ -24,7 +24,7 @@
         <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
     </samlp:Status>
     <saml:Assertion ID="id2" Version="2.0" IssueInstant="2016-02-09T09:30:10.5Z">
-        <saml:Issuer NameQualifier="urn:e-health-suisse:community-index">urn:oid:2.999.1</saml:Issuer>
+        <saml:Issuer NameQualifier="urn:e-health-suisse:community-index">urn:oid:1.2.3</saml:Issuer>
         <saml:Statement xsi:type="xacml-saml:XACMLPolicyStatementType">
             <ns5:PolicySet xmlns:ns3="urn:ihe-d:hl7-org:v3"
                            xmlns:ns5="urn:oasis:names:tc:xacml:2.0:policy:schema:os"
diff --git a/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Component.java b/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Component.java
index 0d8ee2fc24..6a619c74b6 100644
--- a/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Component.java
+++ b/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Component.java
@@ -16,18 +16,11 @@
 package org.openehealth.ipf.platform.camel.ihe.xacml20.chppq2;
 
 import org.apache.camel.Endpoint;
-import org.openehealth.ipf.commons.ihe.ws.JaxWsClientFactory;
 import org.openehealth.ipf.commons.ihe.ws.WsInteractionId;
 import org.openehealth.ipf.commons.ihe.ws.WsTransactionConfiguration;
 import org.openehealth.ipf.commons.ihe.xacml20.CH_PPQ;
 import org.openehealth.ipf.commons.ihe.xacml20.audit.ChPpqAuditDataset;
-import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.ResponseType;
-import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.protocol.XACMLPolicyQueryType;
 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 org.openehealth.ipf.platform.camel.ihe.xacml20.Xacml20Endpoint;
 
 import java.util.Map;
 
@@ -43,12 +36,7 @@ public ChPpq2Component() {
 
     @Override
     protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) {
-        return new Xacml20Endpoint(uri, remaining, this, parameters, ChPpq2Service.class) {
-            @Override
-            public AbstractWsProducer<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>, ?, ?> getProducer(AbstractWsEndpoint<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>> endpoint, JaxWsClientFactory<ChPpqAuditDataset> clientFactory) {
-                return new SimpleWsProducer<>(this, clientFactory, XACMLPolicyQueryType.class, ResponseType.class);
-            }
-        };
+        return new ChPpq2Endpoint(uri, remaining, this, parameters);
     }
 
 }
diff --git a/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Endpoint.java b/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Endpoint.java
new file mode 100644
index 0000000000..973125970a
--- /dev/null
+++ b/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Endpoint.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.openehealth.ipf.platform.camel.ihe.xacml20.chppq2;
+
+import org.openehealth.ipf.commons.ihe.ws.JaxWsClientFactory;
+import org.openehealth.ipf.commons.ihe.ws.WsInteractionId;
+import org.openehealth.ipf.commons.ihe.ws.WsTransactionConfiguration;
+import org.openehealth.ipf.commons.ihe.xacml20.audit.ChPpqAuditDataset;
+import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.ResponseType;
+import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.protocol.XACMLPolicyQueryType;
+import org.openehealth.ipf.platform.camel.ihe.ws.*;
+import org.openehealth.ipf.platform.camel.ihe.xacml20.Xacml20Endpoint;
+
+import java.util.Map;
+
+public class ChPpq2Endpoint extends Xacml20Endpoint {
+
+    public ChPpq2Endpoint(
+            String endpointUri,
+            String address,
+            AbstractWsComponent<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>, ? extends WsInteractionId<WsTransactionConfiguration<ChPpqAuditDataset>>> component,
+            Map<String, Object> parameters)
+    {
+        super(endpointUri, address, component, parameters, ChPpq2Service.class);
+    }
+
+    @Override
+    protected AbstractWebService getCustomServiceInstance(AbstractWsEndpoint<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>> endpoint) {
+        return new ChPpq2Service(endpoint.getHomeCommunityId());
+    }
+
+    @Override
+    public AbstractWsProducer<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>, ?, ?> getProducer(AbstractWsEndpoint<ChPpqAuditDataset, WsTransactionConfiguration<ChPpqAuditDataset>> endpoint, JaxWsClientFactory<ChPpqAuditDataset> clientFactory) {
+        return new SimpleWsProducer<>(this, clientFactory, XACMLPolicyQueryType.class, ResponseType.class);
+    }
+
+}
diff --git a/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Service.java b/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Service.java
index 8738d28ce7..7e2f8776fd 100644
--- a/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Service.java
+++ b/platform-camel/ihe/xacml20/src/main/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Service.java
@@ -16,7 +16,7 @@
 package org.openehealth.ipf.platform.camel.ihe.xacml20.chppq2;
 
 import lombok.extern.slf4j.Slf4j;
-import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Utils;
+import org.openehealth.ipf.commons.ihe.xacml20.ChPpqMessageCreator;
 import org.openehealth.ipf.commons.ihe.xacml20.chppq2.ChPpq2PortType;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.ResponseType;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.xacml20.saml.protocol.XACMLPolicyQueryType;
@@ -30,15 +30,19 @@
 @Slf4j
 public class ChPpq2Service extends AbstractWebService implements ChPpq2PortType {
 
+    private final ChPpqMessageCreator messageCreator;
+
+    public ChPpq2Service(String homeCommunityId) {
+        this.messageCreator = new ChPpqMessageCreator(homeCommunityId);
+    }
+
     @Override
     public ResponseType policyQuery(XACMLPolicyQueryType request) {
         var result = process(request);
         var exception = Exchanges.extractException(result);
         if (exception != null) {
             log.debug(getClass().getSimpleName() + " service failed", exception);
-            var response = Xacml20Utils.createXacmlQueryResponse("urn:oasis:names:tc:SAML:2.0:status:Responder");
-            response.getStatus().setStatusMessage(exception.getMessage());
-            return response;
+            return messageCreator.createNegativePolicyQueryResponse(exception);
         }
         return result.getMessage().getBody(ResponseType.class);
     }
diff --git a/platform-camel/ihe/xacml20/src/test/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Test.java b/platform-camel/ihe/xacml20/src/test/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Test.java
index ac682a763c..793ab19d2e 100644
--- a/platform-camel/ihe/xacml20/src/test/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Test.java
+++ b/platform-camel/ihe/xacml20/src/test/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2Test.java
@@ -25,7 +25,9 @@
 import org.openehealth.ipf.commons.audit.codes.ParticipantObjectTypeCodeRole;
 import org.openehealth.ipf.commons.audit.model.AuditMessage;
 import org.openehealth.ipf.commons.audit.types.CodedValueType;
+import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Status;
 import org.openehealth.ipf.commons.ihe.xacml20.Xacml20Utils;
+import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.assertion.AssertionType;
 import org.openehealth.ipf.commons.ihe.xacml20.stub.saml20.protocol.ResponseType;
 import org.openehealth.ipf.platform.camel.ihe.ws.StandardTestContainer;
 
@@ -77,17 +79,19 @@ private static void checkCodeValueType(CodedValueType value, String[]... allowed
 
     @Test
     public void testQueryPerPatientIdSuccess() throws Exception {
-        testQueryPerPatientId("success", "urn:oasis:names:tc:SAML:2.0:status:Success", EventOutcomeIndicator.Success);
+        testQueryPerPatientId("success", Xacml20Status.SUCCESS, EventOutcomeIndicator.Success, "urn:oid:1.2.3");
     }
 
     @Test
     public void testQueryPerPatientIdFailure() throws Exception {
-        testQueryPerPatientId("failure", "urn:oasis:names:tc:SAML:2.0:status:Responder", EventOutcomeIndicator.SeriousFailure);
+        testQueryPerPatientId("failure", Xacml20Status.RESPONDER_ERROR, EventOutcomeIndicator.SeriousFailure, "urn:oid:1.2.4");
     }
 
-    private void testQueryPerPatientId(String suffix, String statusCode, EventOutcomeIndicator outcomeIndicator) throws Exception {
+    private void testQueryPerPatientId(String suffix, Xacml20Status status, EventOutcomeIndicator outcomeIndicator, String homeCommunityId) throws Exception {
         var response = (ResponseType) send(getUri(suffix), loadFile("query-per-patient-id.xml"), ResponseType.class);
-        assertEquals(statusCode, response.getStatus().getStatusCode().getValue());
+        assertEquals(status.getCode(), response.getStatus().getStatusCode().getValue());
+        var assertion = (AssertionType) response.getAssertionOrEncryptedAssertion().get(0);
+        assertEquals(homeCommunityId, assertion.getIssuer().getValue());
 
         List messages = getAuditSender().getMessages();
         assertEquals(2, messages.size());
@@ -128,17 +132,17 @@ private void testQueryPerPatientId(String suffix, String statusCode, EventOutcom
 
     @Test
     public void testQueryPerPolicyIdSuccess() throws Exception {
-        testQueryPerPolicyId("success", "urn:oasis:names:tc:SAML:2.0:status:Success", EventOutcomeIndicator.Success);
+        testQueryPerPolicyId("success", Xacml20Status.SUCCESS, EventOutcomeIndicator.Success);
     }
 
     @Test
     public void testQueryPerPolicyIdFailure() throws Exception {
-        testQueryPerPolicyId("failure", "urn:oasis:names:tc:SAML:2.0:status:Responder", EventOutcomeIndicator.SeriousFailure);
+        testQueryPerPolicyId("failure", Xacml20Status.RESPONDER_ERROR, EventOutcomeIndicator.SeriousFailure);
     }
 
-    private void testQueryPerPolicyId(String suffix, String statusCode, EventOutcomeIndicator outcomeIndicator) throws Exception {
+    private void testQueryPerPolicyId(String suffix, Xacml20Status status, EventOutcomeIndicator outcomeIndicator) throws Exception {
         var response = (ResponseType) send(getUri(suffix), loadFile("query-per-policy-id.xml"), ResponseType.class);
-        assertEquals(statusCode, response.getStatus().getStatusCode().getValue());
+        assertEquals(status.getCode(), response.getStatus().getStatusCode().getValue());
 
         List messages = getAuditSender().getMessages();
         assertEquals(2, messages.size());
diff --git a/platform-camel/ihe/xacml20/src/test/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2TestRouteBuilder.java b/platform-camel/ihe/xacml20/src/test/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2TestRouteBuilder.java
index b589921aca..0acf06c8a2 100644
--- a/platform-camel/ihe/xacml20/src/test/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2TestRouteBuilder.java
+++ b/platform-camel/ihe/xacml20/src/test/java/org/openehealth/ipf/platform/camel/ihe/xacml20/chppq2/ChPpq2TestRouteBuilder.java
@@ -35,7 +35,7 @@ public class ChPpq2TestRouteBuilder extends RouteBuilder {
     public void configure() throws Exception {
 
         // sends a correct response with status "success"
-        from("ch-ppq2:ch-ppq-success")
+        from("ch-ppq2:ch-ppq-success?homeCommunityId=urn:oid:1.2.3")
                 .process(chPpq2RequestValidator())
                 .process(exchange -> {
                     var stream = ChPpq2TestRouteBuilder.class.getClassLoader().getResourceAsStream("messages/chppq2/ppq-query-backend-response.xml");
@@ -51,7 +51,7 @@ public void configure() throws Exception {
                 .process(chPpq2ResponseValidator());
 
         // sends a correct response with status "failure"
-        from("ch-ppq2:ch-ppq-failure")
+        from("ch-ppq2:ch-ppq-failure?homeCommunityId=urn:oid:1.2.4")
                 .process(chPpq2RequestValidator())
                 .throwException(new RuntimeException("Alles schlimm..."));
 
diff --git a/src/site/changes.xml b/src/site/changes.xml
index 1883362d8f..d77f677355 100644
--- a/src/site/changes.xml
+++ b/src/site/changes.xml
@@ -24,6 +24,12 @@
     </properties>
     <body>
         <release version="4.x.x" description="IPF 4.x.x" date="tbd">
+            <action issue="431" dev="unixoid" type="fix">
+                Fix error handling in CH:PPQ-2
+            </action>
+            <action issue="430" dev="stanojevic-boris" type="update">
+                Relaxed CXi validation in XDS according to CP-ITI-1292
+            </action>
             <action issue="429" dev="unixoid" type="add">
                 Add Spring Boot starter for CH:PPQ
             </action>