From 7779f972cbd4478dfefd70bdffce81868ec98c4f Mon Sep 17 00:00:00 2001 From: Alex Kontsur Date: Thu, 17 Oct 2024 13:47:56 +0400 Subject: [PATCH] EPA-282 * CetpEventMapper implementation is added * CETPServerHandler is extended from AbstractCETPEventHandler --- .../1.0.0-SNAPSHOT/_remote.repositories | 2 +- .../lib-cetp-1.0.0-SNAPSHOT-sources.jar | Bin 26693 -> 30620 bytes .../lib-cetp-1.0.0-SNAPSHOT.jar | Bin 65657 -> 75223 bytes .../1.0.0-SNAPSHOT/maven-metadata-local.xml | 8 +- .../lib-cetp/maven-metadata-local.xml | 2 +- .../ps/service/cetp/CETPServerHandler.java | 190 +++++++----------- .../cetp/CETPServerHandlerFactory.java | 6 +- .../ps/service/cetp/codec/CETPDecoder.java | 30 ++- .../cetp/codec/CETPDecoderFactory.java | 5 +- .../cetp/mapper/event/EventMapper.java | 20 ++ .../cetp/mapper/event/EventTypeMapper.java | 13 ++ .../cetp/mapper/event/ParameterMapper.java | 12 ++ .../cetp/mapper/event/SeverityTypeMapper.java | 14 ++ src/main/resources/application.properties | 4 +- .../service/cetp/event/EventMapperTest.java | 62 ++++++ .../ps/service/gematik/CardInsertedTest.java | 60 +++--- 16 files changed, 251 insertions(+), 177 deletions(-) create mode 100644 src/main/java/health/ere/ps/service/cetp/mapper/event/EventMapper.java create mode 100644 src/main/java/health/ere/ps/service/cetp/mapper/event/EventTypeMapper.java create mode 100644 src/main/java/health/ere/ps/service/cetp/mapper/event/ParameterMapper.java create mode 100644 src/main/java/health/ere/ps/service/cetp/mapper/event/SeverityTypeMapper.java create mode 100644 src/test/java/health/ere/ps/service/cetp/event/EventMapperTest.java diff --git a/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/_remote.repositories b/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/_remote.repositories index 723a6a97..262fc994 100644 --- a/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/_remote.repositories +++ b/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/_remote.repositories @@ -1,5 +1,5 @@ #NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. -#Wed Oct 16 22:40:04 AMT 2024 +#Thu Oct 17 14:12:52 AMT 2024 lib-cetp-1.0.0-SNAPSHOT-sources.jar>= lib-cetp-1.0.0-SNAPSHOT.jar>= lib-cetp-1.0.0-SNAPSHOT.pom>= diff --git a/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/lib-cetp-1.0.0-SNAPSHOT-sources.jar b/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/lib-cetp-1.0.0-SNAPSHOT-sources.jar index d008e846e32cb35ec2567c9a58c46400ef12cd75..40f564ac061019d8f4e6b414c26fcab90dd8d335 100644 GIT binary patch delta 6572 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 CWc$7V diff --git a/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/lib-cetp-1.0.0-SNAPSHOT.jar b/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/lib-cetp-1.0.0-SNAPSHOT.jar index 0797571407c24fac502e97aaf41d36dceab41751..afe4a410f4ba9af51093377eafdb3d9876676e29 100644 GIT binary patch delta 17411 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 diff --git a/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/maven-metadata-local.xml b/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/maven-metadata-local.xml index fae84606..6fff50d9 100644 --- a/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/maven-metadata-local.xml +++ b/project-repo/de/servicehealth/lib-cetp/1.0.0-SNAPSHOT/maven-metadata-local.xml @@ -3,7 +3,7 @@ de.servicehealth lib-cetp - 20241016183958 + 20241017101246 true @@ -11,18 +11,18 @@ pom 1.0.0-SNAPSHOT - 20241016183958 + 20241017101246 jar 1.0.0-SNAPSHOT - 20241016183958 + 20241017101246 sources jar 1.0.0-SNAPSHOT - 20241016183958 + 20241017101246 diff --git a/project-repo/de/servicehealth/lib-cetp/maven-metadata-local.xml b/project-repo/de/servicehealth/lib-cetp/maven-metadata-local.xml index 61eaba45..5c33e583 100644 --- a/project-repo/de/servicehealth/lib-cetp/maven-metadata-local.xml +++ b/project-repo/de/servicehealth/lib-cetp/maven-metadata-local.xml @@ -6,6 +6,6 @@ 1.0.0-SNAPSHOT - 20241016183958 + 20241017101246 diff --git a/src/main/java/health/ere/ps/service/cetp/CETPServerHandler.java b/src/main/java/health/ere/ps/service/cetp/CETPServerHandler.java index 9a10ada5..780db06c 100644 --- a/src/main/java/health/ere/ps/service/cetp/CETPServerHandler.java +++ b/src/main/java/health/ere/ps/service/cetp/CETPServerHandler.java @@ -2,22 +2,21 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; -import de.gematik.ws.conn.eventservice.v7.Event; +import de.health.service.cetp.AbstractCETPEventHandler; import de.health.service.cetp.cardlink.CardlinkWebsocketClient; import de.servicehealth.config.api.IUserConfigurations; import health.ere.ps.config.RuntimeConfig; import health.ere.ps.service.cetp.tracker.TrackerService; import health.ere.ps.service.gematik.PharmacyService; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandlerAdapter; import jakarta.json.Json; import jakarta.json.JsonArrayBuilder; import org.apache.commons.lang3.tuple.Pair; import org.hl7.fhir.r4.model.Bundle; import org.hl7.fhir.r4.model.Bundle.BundleEntryComponent; +import org.hl7.fhir.r4.model.Identifier; +import org.hl7.fhir.r4.model.Task; import org.jboss.logging.MDC; -import java.net.InetSocketAddress; import java.util.Map; import java.util.UUID; import java.util.logging.Level; @@ -26,13 +25,12 @@ import static de.health.service.cetp.utils.Utils.printException; -public class CETPServerHandler extends ChannelInboundHandlerAdapter { +public class CETPServerHandler extends AbstractCETPEventHandler { private static final Logger log = Logger.getLogger(CETPServerHandler.class.getName()); TrackerService trackerService; PharmacyService pharmacyService; - CardlinkWebsocketClient cardlinkWebsocketClient; IParser parser = FhirContext.forR4().newXmlParser(); @@ -41,130 +39,85 @@ public CETPServerHandler( PharmacyService pharmacyService, CardlinkWebsocketClient cardlinkWebsocketClient ) { + super(cardlinkWebsocketClient); this.trackerService = trackerService; this.pharmacyService = pharmacyService; - this.cardlinkWebsocketClient = cardlinkWebsocketClient; } @Override - public void handlerAdded(ChannelHandlerContext ctx) { + protected String getTopicName() { + return "CARD/INSERTED"; } @Override - public void handlerRemoved(ChannelHandlerContext ctx) { - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) { - try { - String correlationId = UUID.randomUUID().toString(); - MDC.put("requestCorrelationId", correlationId); // Keep MDC name in snyc with virtual-nfc-cardlink - cardlinkWebsocketClient.connect(); - - @SuppressWarnings("unchecked") - Pair input = (Pair) msg; - Event event = input.getKey(); - - if (event.getTopic().equals("CARD/INSERTED")) { - final Map eventMap = event.getMessage().getParameter().stream() - .collect(Collectors.toMap(Event.Message.Parameter::getKey, Event.Message.Parameter::getValue)); - - // Keep MDC names in sync with virtual-nfc-cardlink - MDC.put("iccsn", eventMap.getOrDefault("ICCSN", "NoICCSNProvided")); - MDC.put("ctid", eventMap.getOrDefault("CtID", "NoCtIDProvided")); - MDC.put("slot", eventMap.getOrDefault("SlotID", "NoSlotIDProvided")); - log.fine("CARD/INSERTED event received with the following payload: %s".formatted(eventMap)); - - if ("EGK".equalsIgnoreCase(eventMap.get("CardType")) && eventMap.containsKey("CardHandle") && eventMap.containsKey("SlotID") && eventMap.containsKey("CtID")) { - String cardHandle = eventMap.get("CardHandle"); - Integer slotId = Integer.parseInt(eventMap.get("SlotID")); - String ctId = eventMap.get("CtID"); - String iccsn = eventMap.get("ICCSN"); - Long endTime = System.currentTimeMillis(); - - - String paramsStr = event.getMessage().getParameter().stream() - .filter(p -> !p.getKey().equals("CardHolderName")) - .map(p -> String.format("key=%s value=%s", p.getKey(), p.getValue())).collect(Collectors.joining(", ")); - - log.fine(String.format("[%s] Card inserted: params: %s", correlationId, paramsStr)); - try { - IUserConfigurations uc = input.getValue(); - RuntimeConfig runtimeConfig = new RuntimeConfig(uc); - Pair pair = pharmacyService.getEPrescriptionsForCardHandle( - correlationId, cardHandle, null, runtimeConfig - ); - Bundle bundle = pair.getKey(); - String eventId = pair.getValue(); - String xml = parser.encodeToString(bundle); - cardlinkWebsocketClient.sendJson(correlationId, iccsn, "eRezeptTokensFromAVS", Map.of("slotId", slotId, "ctId", ctId, "tokens", xml)); - - JsonArrayBuilder bundles = prepareBundles(correlationId, bundle, runtimeConfig); - cardlinkWebsocketClient.sendJson(correlationId, iccsn, "eRezeptBundlesFromAVS", Map.of("slotId", slotId, "ctId", ctId, "bundles", bundles)); - - cardlinkWebsocketClient.sendJson(correlationId, iccsn, "vsdmSensorData", Map.of("slotId", slotId, "ctId", ctId, "endTime", endTime, "eventId", eventId)); - - trackerService.submit(ctId, uc.getMandantId(), uc.getWorkplaceId(), uc.getClientSystemId()); - } catch (Exception e ) { - log.log(Level.WARNING, String.format("[%s] Could not get prescription for Bundle", correlationId), e); - - if (e instanceof de.gematik.ws.conn.vsds.vsdservice.v5.FaultMessage faultMessage) { - String code = faultMessage.getFaultInfo().getTrace().get(0).getCode().toString(); - cardlinkWebsocketClient.sendJson(correlationId, iccsn, "vsdmSensorData", Map.of("slotId", slotId, "ctId", ctId, "endTime", endTime, "err", code)); - } - if (e instanceof de.gematik.ws.conn.eventservice.wsdl.v7.FaultMessage faultMessage) { - String code = faultMessage.getFaultInfo().getTrace().get(0).getCode().toString(); - cardlinkWebsocketClient.sendJson(correlationId, iccsn, "vsdmSensorData", Map.of("slotId", slotId, "ctId", ctId, "endTime", endTime, "err", code)); - } - - String error = printException(e); - cardlinkWebsocketClient.sendJson( - correlationId, - iccsn, - "receiveTasklistError", - Map.of("slotId", slotId, "cardSessionId", "null", "status", 500, "tistatus", "500", "errormessage", error) - ); - } - } else { - String msgFormat = "Ignored \"CARD/INSERTED\" event=%s: values=%s"; - log.log(Level.INFO, String.format(msgFormat, event.getMessage(), eventMap)); + protected void processEvent(IUserConfigurations uc, Map paramsMap) { + // Keep MDC names in sync with virtual-nfc-cardlink + String correlationId = UUID.randomUUID().toString(); + MDC.put("requestCorrelationId", correlationId); + MDC.put("iccsn", paramsMap.getOrDefault("ICCSN", "NoICCSNProvided")); + MDC.put("ctid", paramsMap.getOrDefault("CtID", "NoCtIDProvided")); + MDC.put("slot", paramsMap.getOrDefault("SlotID", "NoSlotIDProvided")); + log.fine("CARD/INSERTED event received with the following payload: %s".formatted(paramsMap)); + + if ("EGK".equalsIgnoreCase(paramsMap.get("CardType")) && paramsMap.containsKey("CardHandle") && paramsMap.containsKey("SlotID") && paramsMap.containsKey("CtID")) { + String cardHandle = paramsMap.get("CardHandle"); + Integer slotId = Integer.parseInt(paramsMap.get("SlotID")); + String ctId = paramsMap.get("CtID"); + String iccsn = paramsMap.get("ICCSN"); + Long endTime = System.currentTimeMillis(); + + + String paramsStr = paramsMap.entrySet().stream() + .filter(p -> !p.getKey().equals("CardHolderName")) + .map(p -> String.format("key=%s value=%s", p.getKey(), p.getValue())).collect(Collectors.joining(", ")); + + log.fine(String.format("[%s] Card inserted: params: %s", correlationId, paramsStr)); + try { + RuntimeConfig runtimeConfig = new RuntimeConfig(uc); + Pair pair = pharmacyService.getEPrescriptionsForCardHandle( + correlationId, cardHandle, null, runtimeConfig + ); + Bundle bundle = pair.getKey(); + String eventId = pair.getValue(); + String xml = parser.encodeToString(bundle); + cardlinkWebsocketClient.sendJson(correlationId, iccsn, "eRezeptTokensFromAVS", Map.of("slotId", slotId, "ctId", ctId, "tokens", xml)); + + JsonArrayBuilder bundles = prepareBundles(correlationId, bundle, runtimeConfig); + cardlinkWebsocketClient.sendJson(correlationId, iccsn, "eRezeptBundlesFromAVS", Map.of("slotId", slotId, "ctId", ctId, "bundles", bundles)); + + cardlinkWebsocketClient.sendJson(correlationId, iccsn, "vsdmSensorData", Map.of("slotId", slotId, "ctId", ctId, "endTime", endTime, "eventId", eventId)); + + trackerService.submit(ctId, uc.getMandantId(), uc.getWorkplaceId(), uc.getClientSystemId()); + } catch (Exception e ) { + log.log(Level.WARNING, String.format("[%s] Could not get prescription for Bundle", correlationId), e); + + if (e instanceof de.gematik.ws.conn.vsds.vsdservice.v5.FaultMessage faultMessage) { + String code = faultMessage.getFaultInfo().getTrace().get(0).getCode().toString(); + cardlinkWebsocketClient.sendJson(correlationId, iccsn, "vsdmSensorData", Map.of("slotId", slotId, "ctId", ctId, "endTime", endTime, "err", code)); + } + if (e instanceof de.gematik.ws.conn.eventservice.wsdl.v7.FaultMessage faultMessage) { + String code = faultMessage.getFaultInfo().getTrace().get(0).getCode().toString(); + cardlinkWebsocketClient.sendJson(correlationId, iccsn, "vsdmSensorData", Map.of("slotId", slotId, "ctId", ctId, "endTime", endTime, "err", code)); } - } - - } finally { - cardlinkWebsocketClient.close(); - MDC.clear(); - } - } - - @Override - public void channelRegistered(ChannelHandlerContext ctx) throws Exception { - if (log.isLoggable(Level.FINE)) { - String port = "unknown"; - if (ctx.channel().localAddress() instanceof InetSocketAddress inetSocketAddress) { - port = String.valueOf(inetSocketAddress.getPort()); - } - log.fine(String.format("New CETP connection established (on port %s)", port)); - } - super.channelRegistered(ctx); - } - @Override - public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { - if (log.isLoggable(Level.FINE)) { - String port = "unknown"; - if (ctx.channel().localAddress() instanceof InetSocketAddress inetSocketAddress) { - port = String.valueOf(inetSocketAddress.getPort()); + String error = printException(e); + cardlinkWebsocketClient.sendJson( + correlationId, + iccsn, + "receiveTasklistError", + Map.of("slotId", slotId, "cardSessionId", "null", "status", 500, "tistatus", "500", "errormessage", error) + ); } - log.fine(String.format("CETP connection was closed (on port %s)", port)); + } else { + String msgFormat = "Ignored \"CARD/INSERTED\" values=%s"; + log.log(Level.INFO, String.format(msgFormat, paramsMap)); } - super.channelUnregistered(ctx); } private JsonArrayBuilder prepareBundles(String correlationId, Bundle bundle, RuntimeConfig runtimeConfig) { JsonArrayBuilder bundles = Json.createArrayBuilder(); for (BundleEntryComponent entry : bundle.getEntry()) { - if (entry.getResource() instanceof org.hl7.fhir.r4.model.Task) { + if (entry.getResource() instanceof Task task) { /* * * @@ -178,9 +131,8 @@ private JsonArrayBuilder prepareBundles(String correlationId, Bundle bundle, Run * */ - org.hl7.fhir.r4.model.Task task = (org.hl7.fhir.r4.model.Task) entry.getResource(); - String taskId = task.getIdentifier().stream().filter(t -> "https://gematik.de/fhir/erp/NamingSystem/GEM_ERP_NS_PrescriptionId".equals(t.getSystem())).map(t -> t.getValue()).findAny().orElse(null); - String accessCode = task.getIdentifier().stream().filter(t -> "https://gematik.de/fhir/erp/NamingSystem/GEM_ERP_NS_AccessCode".equals(t.getSystem())).map(t -> t.getValue()).findAny().orElse(null); + String taskId = task.getIdentifier().stream().filter(t -> "https://gematik.de/fhir/erp/NamingSystem/GEM_ERP_NS_PrescriptionId".equals(t.getSystem())).map(Identifier::getValue).findAny().orElse(null); + String accessCode = task.getIdentifier().stream().filter(t -> "https://gematik.de/fhir/erp/NamingSystem/GEM_ERP_NS_AccessCode".equals(t.getSystem())).map(Identifier::getValue).findAny().orElse(null); log.fine("TaskId: " + taskId + " AccessCode: " + accessCode); String token = "/Task/" + taskId + "/$accept?ac=" + accessCode; try { @@ -193,10 +145,4 @@ private JsonArrayBuilder prepareBundles(String correlationId, Bundle bundle, Run } return bundles; } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - log.log(Level.SEVERE, "Caught an exception handling CETP input", cause); - ctx.close(); - } } diff --git a/src/main/java/health/ere/ps/service/cetp/CETPServerHandlerFactory.java b/src/main/java/health/ere/ps/service/cetp/CETPServerHandlerFactory.java index 46026270..89cd5e4b 100644 --- a/src/main/java/health/ere/ps/service/cetp/CETPServerHandlerFactory.java +++ b/src/main/java/health/ere/ps/service/cetp/CETPServerHandlerFactory.java @@ -43,7 +43,7 @@ public CETPServerHandlerFactory( } @Override - public ChannelInboundHandler build(KonnektorConfig kc) { + public ChannelInboundHandler[] build(KonnektorConfig kc) { CardlinkWebsocketClient cardlinkWebsocketClient = new CardlinkWebsocketClient( kc.getCardlinkEndpoint(), new EreJwtConfigurator( @@ -56,6 +56,8 @@ public ChannelInboundHandler build(KonnektorConfig kc) { kc.getCardlinkEndpoint(), cardlinkWebsocketClient.connected() ); - return new CETPServerHandler(trackerService, pharmacyService, cardlinkWebsocketClient); + return new CETPServerHandler[] { + new CETPServerHandler(trackerService, pharmacyService, cardlinkWebsocketClient) + }; } } diff --git a/src/main/java/health/ere/ps/service/cetp/codec/CETPDecoder.java b/src/main/java/health/ere/ps/service/cetp/codec/CETPDecoder.java index d0d296f6..e78fa941 100644 --- a/src/main/java/health/ere/ps/service/cetp/codec/CETPDecoder.java +++ b/src/main/java/health/ere/ps/service/cetp/codec/CETPDecoder.java @@ -1,13 +1,14 @@ package health.ere.ps.service.cetp.codec; import de.gematik.ws.conn.eventservice.v7.Event; +import de.health.service.cetp.domain.eventservice.event.DecodeResult; +import de.health.service.cetp.domain.eventservice.event.mapper.CetpEventMapper; import de.servicehealth.config.api.IUserConfigurations; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.JAXBException; -import org.apache.commons.lang3.tuple.Pair; import java.io.StringReader; import java.nio.charset.StandardCharsets; @@ -20,7 +21,6 @@ public class CETPDecoder extends ByteToMessageDecoder { private static final Logger log = Logger.getLogger(CETPDecoder.class.getName()); static JAXBContext jaxbContext; - static { try { jaxbContext = JAXBContext.newInstance(Event.class); @@ -28,37 +28,35 @@ public class CETPDecoder extends ByteToMessageDecoder { log.log(Level.SEVERE, "Failed to create JAXB context", e); } } - IUserConfigurations userConfigurations; - public CETPDecoder() { + IUserConfigurations configurations; + CetpEventMapper eventMapper; + public CETPDecoder() { } - public CETPDecoder(IUserConfigurations userConfigurations) { - this.userConfigurations = userConfigurations; + public CETPDecoder(IUserConfigurations configurations, CetpEventMapper eventMapper) { + this.configurations = configurations; + this.eventMapper = eventMapper; } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) { // (2) - if(!in.isReadable(4)) { - return; - } - byte[] header = new byte[4]; + if (!in.isReadable(4)) { + return; + } + byte[] header = new byte[4]; in.readBytes(header); - - if(header[0] != 'C' || header[1] != 'E' || header[2] != 'T' || header[3] != 'P') { + if (header[0] != 'C' || header[1] != 'E' || header[2] != 'T' || header[3] != 'P') { throw new IllegalArgumentException("Invalid CETP header"); } int lengthOfMessage = in.readInt(); - String message = in.readCharSequence(lengthOfMessage, StandardCharsets.UTF_8).toString(); - log.info(message); - try { Event eventType = (Event) jaxbContext.createUnmarshaller().unmarshal(new StringReader(message)); - out.add(Pair.of(eventType, userConfigurations)); + out.add(new DecodeResult(eventMapper.toDomain(eventType), configurations)); } catch (JAXBException e) { log.log(Level.SEVERE, "Failed to unmarshal CETP message", e); } diff --git a/src/main/java/health/ere/ps/service/cetp/codec/CETPDecoderFactory.java b/src/main/java/health/ere/ps/service/cetp/codec/CETPDecoderFactory.java index ba588af7..b68eaa6e 100644 --- a/src/main/java/health/ere/ps/service/cetp/codec/CETPDecoderFactory.java +++ b/src/main/java/health/ere/ps/service/cetp/codec/CETPDecoderFactory.java @@ -1,6 +1,7 @@ package health.ere.ps.service.cetp.codec; import de.health.service.cetp.codec.CETPEventDecoderFactory; +import de.health.service.cetp.domain.eventservice.event.mapper.CetpEventMapper; import de.servicehealth.config.api.IUserConfigurations; import io.netty.channel.ChannelInboundHandler; import jakarta.enterprise.context.ApplicationScoped; @@ -9,7 +10,7 @@ public class CETPDecoderFactory implements CETPEventDecoderFactory { @Override - public ChannelInboundHandler build(IUserConfigurations userConfigurations) { - return new CETPDecoder(userConfigurations); + public ChannelInboundHandler build(IUserConfigurations configurations, CetpEventMapper eventMapper) { + return new CETPDecoder(configurations, eventMapper); } } diff --git a/src/main/java/health/ere/ps/service/cetp/mapper/event/EventMapper.java b/src/main/java/health/ere/ps/service/cetp/mapper/event/EventMapper.java new file mode 100644 index 00000000..bd2cdaf8 --- /dev/null +++ b/src/main/java/health/ere/ps/service/cetp/mapper/event/EventMapper.java @@ -0,0 +1,20 @@ +package health.ere.ps.service.cetp.mapper.event; + +import de.gematik.ws.conn.eventservice.v7.Event; +import de.health.service.cetp.domain.eventservice.event.CetpEvent; +import de.health.service.cetp.domain.eventservice.event.mapper.CetpEventMapper; +import health.ere.ps.service.cetp.mapper.DefaultMappingConfig; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; + +@Mapper( + config = DefaultMappingConfig.class, + uses = {EventTypeMapper.class, SeverityTypeMapper.class, ParameterMapper.class} +) +public abstract class EventMapper implements CetpEventMapper { + + @Override + @Mapping(target = "subscriptionId", source = "subscriptionID") + @Mapping(target = "parameters", source = "message.parameter") + public abstract CetpEvent toDomain(Event soap); +} diff --git a/src/main/java/health/ere/ps/service/cetp/mapper/event/EventTypeMapper.java b/src/main/java/health/ere/ps/service/cetp/mapper/event/EventTypeMapper.java new file mode 100644 index 00000000..084bb159 --- /dev/null +++ b/src/main/java/health/ere/ps/service/cetp/mapper/event/EventTypeMapper.java @@ -0,0 +1,13 @@ +package health.ere.ps.service.cetp.mapper.event; + +import de.gematik.ws.conn.eventservice.v7.EventType; +import de.health.service.cetp.domain.eventservice.event.CetpEventType; +import health.ere.ps.service.cetp.mapper.DefaultMappingConfig; +import org.mapstruct.Mapper; + +@Mapper(config = DefaultMappingConfig.class) +public interface EventTypeMapper { + + CetpEventType toDomain(EventType eventType); + +} diff --git a/src/main/java/health/ere/ps/service/cetp/mapper/event/ParameterMapper.java b/src/main/java/health/ere/ps/service/cetp/mapper/event/ParameterMapper.java new file mode 100644 index 00000000..9e876a32 --- /dev/null +++ b/src/main/java/health/ere/ps/service/cetp/mapper/event/ParameterMapper.java @@ -0,0 +1,12 @@ +package health.ere.ps.service.cetp.mapper.event; + +import de.gematik.ws.conn.eventservice.v7.Event; +import de.health.service.cetp.domain.eventservice.event.CetpParameter; +import health.ere.ps.service.cetp.mapper.DefaultMappingConfig; +import org.mapstruct.Mapper; + +@Mapper(config = DefaultMappingConfig.class) +public interface ParameterMapper { + + CetpParameter toDomain(Event.Message.Parameter soap); +} diff --git a/src/main/java/health/ere/ps/service/cetp/mapper/event/SeverityTypeMapper.java b/src/main/java/health/ere/ps/service/cetp/mapper/event/SeverityTypeMapper.java new file mode 100644 index 00000000..61d21320 --- /dev/null +++ b/src/main/java/health/ere/ps/service/cetp/mapper/event/SeverityTypeMapper.java @@ -0,0 +1,14 @@ +package health.ere.ps.service.cetp.mapper.event; + +import de.gematik.ws.conn.eventservice.v7.EventSeverityType; +import de.gematik.ws.conn.eventservice.v7.EventType; +import de.health.service.cetp.domain.eventservice.event.CetpEventType; +import de.health.service.cetp.domain.eventservice.event.CetpSeverityType; +import health.ere.ps.service.cetp.mapper.DefaultMappingConfig; +import org.mapstruct.Mapper; + +@Mapper(config = DefaultMappingConfig.class) +public interface SeverityTypeMapper { + + CetpSeverityType toDomain(EventSeverityType eventSeverityType); +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a60812a1..c0e74be0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,12 +1,14 @@ # For dev mode purposes, specify the environment variables in this file in a file named .env which # should be located in the root project folder. # -# In regards to file and directory paths, configure the values for the environment variables in the +# In regard to file and directory paths, configure the values for the environment variables in the # .env file to be specific to directory and file paths on your local computer. # # Important! Configure the .env file to be ignored and not checked into the source code repository. quarkus.ssl.native=true +quarkus.http.test-port=8888 + quarkus.http.ssl-port=8443 quarkus.http.ssl.certificate.key-store-file=META-INF/resources/server.keystore quarkus.http.ssl.certificate.key-store-password=password diff --git a/src/test/java/health/ere/ps/service/cetp/event/EventMapperTest.java b/src/test/java/health/ere/ps/service/cetp/event/EventMapperTest.java new file mode 100644 index 00000000..51d21d41 --- /dev/null +++ b/src/test/java/health/ere/ps/service/cetp/event/EventMapperTest.java @@ -0,0 +1,62 @@ +package health.ere.ps.service.cetp.event; + +import de.gematik.ws.conn.eventservice.v7.Event; +import de.gematik.ws.conn.eventservice.v7.EventSeverityType; +import de.gematik.ws.conn.eventservice.v7.EventType; +import de.health.service.cetp.domain.eventservice.event.CetpEvent; +import health.ere.ps.profile.RUDevTestProfile; +import health.ere.ps.service.cetp.mapper.event.EventMapper; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.TestProfile; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@QuarkusTest +@TestProfile(RUDevTestProfile.class) +public class EventMapperTest { + + @Inject + EventMapper eventMapper; + + @Test + public void soapEventMappedCorrectly() { + Event event = new Event(); + event.setTopic("CARD/INSERTED"); + event.setType(EventType.OPERATION); + event.setSeverity(EventSeverityType.INFO); + event.setSubscriptionID("subscriptionID"); + + Event.Message message = new Event.Message(); + Event.Message.Parameter p1 = new Event.Message.Parameter(); + p1.setKey("Key1"); + p1.setValue("Value1"); + message.getParameter().add(p1); + Event.Message.Parameter p2 = new Event.Message.Parameter(); + p2.setKey("Key2"); + p2.setValue("Value2"); + message.getParameter().add(p2); + + event.setMessage(message); + + CetpEvent domain = eventMapper.toDomain(event); + assertEquals(event.getTopic(), domain.getTopic()); + assertEquals(event.getType().value(), domain.getType().getValue()); + assertEquals(event.getSeverity().value(), domain.getSeverity().getValue()); + assertEquals(event.getSubscriptionID(), domain.getSubscriptionId()); + + List parameter = event.getMessage().getParameter(); + assertEquals(parameter.size(), domain.getParameters().size()); + assertParameter(event, domain, 0); + assertParameter(event, domain, 1); + } + + private void assertParameter(Event event, CetpEvent domain, int index) { + List parameter = event.getMessage().getParameter(); + assertEquals(parameter.get(index).getKey(), domain.getParameters().get(index).getKey()); + assertEquals(parameter.get(index).getValue(), domain.getParameters().get(index).getValue()); + } +} diff --git a/src/test/java/health/ere/ps/service/gematik/CardInsertedTest.java b/src/test/java/health/ere/ps/service/gematik/CardInsertedTest.java index 05467374..5645c0ed 100644 --- a/src/test/java/health/ere/ps/service/gematik/CardInsertedTest.java +++ b/src/test/java/health/ere/ps/service/gematik/CardInsertedTest.java @@ -1,53 +1,57 @@ package health.ere.ps.service.gematik; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.io.InputStream; -import java.math.BigInteger; -import java.util.List; -import java.util.Map; - -import org.apache.commons.lang3.tuple.Pair; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; - import de.gematik.ws.conn.eventservice.v7.Event; import de.gematik.ws.conn.vsds.vsdservice.v5.FaultMessage; import de.gematik.ws.tel.error.v2.Error; import de.health.service.cetp.cardlink.CardlinkWebsocketClient; +import de.health.service.cetp.domain.eventservice.event.DecodeResult; import health.ere.ps.config.AppConfig; import health.ere.ps.jmx.ReadEPrescriptionsMXBeanImpl; import health.ere.ps.model.config.UserConfigurations; import health.ere.ps.service.cetp.CETPServerHandler; +import health.ere.ps.service.cetp.mapper.event.EventMapper; import health.ere.ps.service.cetp.tracker.TrackerService; import health.ere.ps.service.idp.BearerTokenService; import io.netty.channel.embedded.EmbeddedChannel; import io.quarkus.test.junit.QuarkusTest; +import jakarta.inject.Inject; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; import jakarta.xml.bind.DatatypeConverter; import jakarta.xml.ws.Holder; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; + +import java.io.InputStream; +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; @QuarkusTest class CardInsertedTest { private static final String READ_VSD_RESPONSE = "H4sIAAAAAAAA/w2M3QqCMBhAXyV8AL+5oj/mQNyKgk3ROaKbKLT8T1L8e/q8OReHwyG+XLlMPDQPwosnbcMykYmM1ViVdWsbadc1R4ChNT9J9eyywowTeD+hb+MKmnqAfukNSlRIMcJrtMN7tMW7zYHAoginmACnxL9TzZxJsGgtcmeWjGNPOZbIIyzzVGt2fo1zVvIrFEqq7BZZwVd7Z81+vSsmfzgVNoFlskDSP8uj5+izAAAA"; + @Inject + EventMapper eventMapper; + @Test void vsdmSensorDataWithEventIdIsSentOnCardInsertedEvent() throws Exception { PharmacyService pharmacyService = spy(createPharmacyService()); @@ -66,7 +70,7 @@ void vsdmSensorDataWithEventIdIsSentOnCardInsertedEvent() throws Exception { String slotIdValue = "3"; String ctIdValue = "CtIDValue"; - channel.writeOneInbound(prepareEvent(slotIdValue, ctIdValue)); + channel.writeOneInbound(decode(slotIdValue, ctIdValue)); channel.pipeline().fireChannelReadComplete(); ArgumentCaptor messageTypeCaptor = ArgumentCaptor.forClass(String.class); @@ -110,7 +114,7 @@ void vsdmSensorDataWithErrorIsSentOnCardInsertedEvent() throws Exception { String slotIdValue = "3"; String ctIdValue = "9"; - channel.writeOneInbound(prepareEvent(slotIdValue, ctIdValue)); + channel.writeOneInbound(decode(slotIdValue, ctIdValue)); channel.pipeline().fireChannelReadComplete(); ArgumentCaptor messageTypeCaptor = ArgumentCaptor.forClass(String.class); @@ -162,7 +166,7 @@ private Holder prepareHolder(PharmacyService pharmacyService) { return holder; } - private Pair prepareEvent(String slotIdValue, String ctIdValue) { + private DecodeResult decode(String slotIdValue, String ctIdValue) { Event event = new Event(); event.setTopic("CARD/INSERTED"); Event.Message message = new Event.Message(); @@ -184,7 +188,7 @@ private Pair prepareEvent(String slotIdValue, String message.getParameter().add(parameterCtId); message.getParameter().add(parameterCardType); event.setMessage(message); - return Pair.of(event, new UserConfigurations()); + return new DecodeResult(eventMapper.toDomain(event), new UserConfigurations()); } private static PharmacyService createPharmacyService() {