From 25d68c8900c2cc791f03ea3db173955ca237fd55 Mon Sep 17 00:00:00 2001 From: Christof Rabensteiner Date: Fri, 12 Jul 2019 15:40:05 +0200 Subject: Allow App To Choose Between MsgResponse Sinks - MZS Schema Change: Add "MsgResponseSinks" element to mzs:DeliveryRequest/Config that allows sender to configure how MsgResponses should be archived. - ConfigUtil: Interpret MsgResponseSink parameters from Spring Environment and merge with ConfigType. - MsgResponseBackend: Send responses to sinks according to MsgResponseSinks in Config - application.yaml: Add MsgResponseSinks parameter to configuration. - Uncouple Sink implementations from java.util.function.Function, because the sink interfaces are going to differ and there is no need to unite them under one interface. - Add and test LogResponseSink, which logs responses to it's logger. - MsgResponse: Add JAXB getter for response. Reason: Can be passed to marshaller. --- .../at/gv/egiz/moazs/backend/LogResponseSink.java | 44 ++++++++++++ .../gv/egiz/moazs/backend/MsgResponseBackend.java | 41 ++++++++++-- .../egiz/moazs/backend/SaveResponseToFileSink.java | 14 ++-- .../at/gv/egiz/moazs/preprocess/ConfigUtil.java | 62 ++++++++++++++--- .../java/at/gv/egiz/moazs/scheme/MsgResponse.java | 7 ++ .../gv/egiz/moazs/scheme/NotificationResponse.java | 9 +++ .../egiz/moazs/scheme/RequestStatusResponse.java | 10 +++ src/main/resources/application.yaml | 60 +++++++++-------- src/main/resources/mzs/app2mzs.xsd | 8 +++ .../java/at/gv/egiz/moazs/LogResponseSinkTest.java | 78 ++++++++++++++++++++++ .../gv/egiz/moazs/SaveResponseToFileSinkTest.java | 2 +- 11 files changed, 286 insertions(+), 49 deletions(-) create mode 100644 src/main/java/at/gv/egiz/moazs/backend/LogResponseSink.java create mode 100644 src/test/java/at/gv/egiz/moazs/LogResponseSinkTest.java (limited to 'src') diff --git a/src/main/java/at/gv/egiz/moazs/backend/LogResponseSink.java b/src/main/java/at/gv/egiz/moazs/backend/LogResponseSink.java new file mode 100644 index 0000000..d419944 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/backend/LogResponseSink.java @@ -0,0 +1,44 @@ +package at.gv.egiz.moazs.backend; + +import at.gv.egiz.moazs.scheme.Marshaller; +import at.gv.egiz.moazs.scheme.MsgResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +import static java.util.concurrent.CompletableFuture.completedFuture; + +@Component +public class LogResponseSink { + + private static final Logger log = LoggerFactory.getLogger(LogResponseSink.class); + private static final String LOG_LEVEL_WARN_MESSAGE = "Will not log response because INFO level is disabled."; + + private final Marshaller msgMarshaller; + + public LogResponseSink(Marshaller msgMarshaller) { + this.msgMarshaller = msgMarshaller; + } + + /** + * Log response to class logger at level=INFO. + * + * @param msgResponse + * @return + */ + public CompletableFuture log(MsgResponse msgResponse) { + + if(log.isInfoEnabled()) { + var builder = new StringBuilder("Received the following Message: \n"); + builder.append(msgMarshaller.marshallXml(msgResponse.getResponseAsJAXBElement())); + log.info(builder.toString()); + } else { + log.warn(LOG_LEVEL_WARN_MESSAGE); + } + + return completedFuture(null); + + } +} diff --git a/src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java b/src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java index 414c2dc..1b17f34 100644 --- a/src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java +++ b/src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java @@ -4,14 +4,13 @@ import at.gv.egiz.moazs.MoaZSException; import at.gv.egiz.moazs.repository.DeliveryRepository; import at.gv.egiz.moazs.scheme.MsgResponse; import at.gv.egiz.moazs.service.MsgService; +import at.gv.zustellung.app2mzs.xsd.MsgResponseSinksType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; -import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; -import java.util.function.Function; import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; import static java.lang.String.format; @@ -27,29 +26,38 @@ public class MsgResponseBackend implements Consumer { private final DeliveryRepository repository; private final Consumer signatureVerifier; - private final Function> sink; + private final SaveResponseToFileSink saveResponseToFileSink; + private final LogResponseSink logResponseSink; @Autowired public MsgResponseBackend(DeliveryRepository repository, Consumer signatureVerifier, - Function> sink) { + SaveResponseToFileSink saveResponseToFileSink, + LogResponseSink logResponseSink) { this.repository = repository; this.signatureVerifier = signatureVerifier; - this.sink = sink; + this.saveResponseToFileSink = saveResponseToFileSink; + this.logResponseSink = logResponseSink; } /** * Performs all {@code MsgResponse}'s Back-End Tasks, such as verifying * its signature and archiving the response. * + * Note: When the signature verification fails, this method will not archive + * the original response (the one that was received from msg) to the sink, + * but an error message. + * * @param responseID refers to MsgResponse Object. */ @Override public void accept(String responseID) { - supplyAsync(() -> verify(responseID)).thenAcceptAsync(sink::apply); + + supplyAsync(() -> verify(responseID)) + .thenAcceptAsync(msgResponse -> applySinks(msgResponse)); } - public MsgResponse verify(String responseID) { + private MsgResponse verify(String responseID) { var response = repository.retrieveResponse(responseID).get(); var builder = moaZSExceptionBuilder().withAllParametersInAnswer(response.getAnswer()); @@ -72,4 +80,23 @@ public class MsgResponseBackend implements Consumer { } + private void applySinks(MsgResponse msgResponse) { + + var sinkParams = getSinkParams(msgResponse); + + if (sinkParams.isSafeResponseToFile()) { + supplyAsync(() -> saveResponseToFileSink.save(msgResponse)); + } + + if (sinkParams.isLogResponse()) { + supplyAsync(() -> logResponseSink.log(msgResponse)); + } + } + + private MsgResponseSinksType getSinkParams(MsgResponse msgResponse) { + var appDeliveryID = msgResponse.getAppDeliveryID(); + var request = repository.retrieveDeliveryRequest(appDeliveryID).get(); + return request.getConfig().getMsgResponseSinks(); + } + } diff --git a/src/main/java/at/gv/egiz/moazs/backend/SaveResponseToFileSink.java b/src/main/java/at/gv/egiz/moazs/backend/SaveResponseToFileSink.java index 02771a9..7da76ef 100644 --- a/src/main/java/at/gv/egiz/moazs/backend/SaveResponseToFileSink.java +++ b/src/main/java/at/gv/egiz/moazs/backend/SaveResponseToFileSink.java @@ -15,7 +15,6 @@ import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.CompletableFuture; -import java.util.function.Function; import static at.gv.egiz.moazs.MoaZSException.moaZSException; import static java.lang.String.format; @@ -23,10 +22,11 @@ import static java.util.concurrent.CompletableFuture.allOf; import static java.util.concurrent.CompletableFuture.supplyAsync; @Component -public class SaveResponseToFileSink implements Function> { +public class SaveResponseToFileSink { private static final Logger log = LoggerFactory.getLogger(SaveResponseToFileSink.class); private static final String SAVING_FAILED_MSG = "Could not save response with AppDeliveryId=%s."; + private static final String SAVE_RESPONSE_MSG = "Saving response to {}..." ; private final SimpleDateFormat isoFormatter; private final Marshaller msgMarshaller; @@ -41,8 +41,13 @@ public class SaveResponseToFileSink implements Function apply(MsgResponse response) { + /** + * Save response and it's binary version to the file system. + * + * @param response + * @return Future that completes when both responses have been written to the file system. + */ + public CompletableFuture save(MsgResponse response) { var responseID = response.getResponseID(); @@ -79,6 +84,7 @@ public class SaveResponseToFileSink implements Function params) { + + var logResponse = booleanOrNull(params.get(LOG_RESPONSE_KEY)); + var saveResponse = booleanOrNull(params.get(SAVE_RESPONSE_TO_FILE_KEY)); + + return msgResponseSinksTypeBuilder() + .withLogResponse(logResponse) + .withLogResponse(saveResponse) + .build(); + } + + private Boolean booleanOrNull(String value) { + return value == null ? null : Boolean.getBoolean(value); + } + + /** * Combine properties of two Configs; {@code primary} overrides {@code fallback}. * @@ -149,6 +167,10 @@ public class ConfigUtil { builder.withMSGClient(merge(primary.getTNVZClient(), fallback.getTNVZClient())); } + if (primary.getMsgResponseSinks() != null) { + builder.withMsgResponseSinks(merge(primary.getMsgResponseSinks(), fallback.getMsgResponseSinks())); + } + return builder.build(); } @@ -214,4 +236,24 @@ public class ConfigUtil { } + private MsgResponseSinksType merge(MsgResponseSinksType primary, MsgResponseSinksType fallback) { + + if (fallback == null) { + return primary; + } + + var builder = msgResponseSinksTypeBuilder(fallback); + + if (primary.isLogResponse() != null) { + builder.withLogResponse(primary.isLogResponse()); + } + + if (primary.isSafeResponseToFile() != null) { + builder.withLogResponse(primary.isSafeResponseToFile()); + } + + return builder.build(); + } + + } diff --git a/src/main/java/at/gv/egiz/moazs/scheme/MsgResponse.java b/src/main/java/at/gv/egiz/moazs/scheme/MsgResponse.java index 80e2059..5370448 100644 --- a/src/main/java/at/gv/egiz/moazs/scheme/MsgResponse.java +++ b/src/main/java/at/gv/egiz/moazs/scheme/MsgResponse.java @@ -3,6 +3,12 @@ package at.gv.egiz.moazs.scheme; import at.gv.egiz.moazs.MoaZSException; import at.gv.zustellung.msg.xsd.DeliveryAnswerType; +import javax.xml.bind.JAXBElement; + +/** + * Represents responses to DeliveryRequests that were received from the msg service. + * @param The type of the response. + */ public abstract class MsgResponse { protected String id; @@ -20,6 +26,7 @@ public abstract class MsgResponse { } public abstract T getResponse(); + public abstract JAXBElement getResponseAsJAXBElement(); public abstract String getAppDeliveryID(); public abstract String getZSDeliveryID(); public abstract DeliveryAnswerType getAnswer(); diff --git a/src/main/java/at/gv/egiz/moazs/scheme/NotificationResponse.java b/src/main/java/at/gv/egiz/moazs/scheme/NotificationResponse.java index de1fd38..784d000 100644 --- a/src/main/java/at/gv/egiz/moazs/scheme/NotificationResponse.java +++ b/src/main/java/at/gv/egiz/moazs/scheme/NotificationResponse.java @@ -3,6 +3,9 @@ package at.gv.egiz.moazs.scheme; import at.gv.egiz.moazs.MoaZSException; import at.gv.zustellung.msg.xsd.DeliveryAnswerType; import at.gv.zustellung.msg.xsd.DeliveryNotificationType; +import at.gv.zustellung.msg.xsd.ObjectFactory; + +import javax.xml.bind.JAXBElement; import static at.gv.zustellung.msg.xsd.DeliveryNotificationType.deliveryNotificationTypeBuilder; @@ -10,6 +13,7 @@ public class NotificationResponse extends MsgResponse private final DeliveryNotificationType notification; private static final String ID_SUFFIX = ".NO"; + private static final ObjectFactory factory = new ObjectFactory(); public NotificationResponse(DeliveryNotificationType notification) { super.id = createResponseId(notification.getAppDeliveryID(), ID_SUFFIX); @@ -25,6 +29,11 @@ public class NotificationResponse extends MsgResponse return notification; } + @Override + public JAXBElement getResponseAsJAXBElement() { + return factory.createDeliveryNotification(notification); + } + @Override public String getAppDeliveryID() { return notification.getAppDeliveryID(); diff --git a/src/main/java/at/gv/egiz/moazs/scheme/RequestStatusResponse.java b/src/main/java/at/gv/egiz/moazs/scheme/RequestStatusResponse.java index 0705698..3b4710b 100644 --- a/src/main/java/at/gv/egiz/moazs/scheme/RequestStatusResponse.java +++ b/src/main/java/at/gv/egiz/moazs/scheme/RequestStatusResponse.java @@ -4,6 +4,9 @@ import at.gv.egiz.moazs.MoaZSException; import at.gv.zustellung.msg.xsd.DeliveryAnswerType; import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; import at.gv.zustellung.msg.xsd.ErrorInfoType; +import at.gv.zustellung.msg.xsd.ObjectFactory; + +import javax.xml.bind.JAXBElement; import static at.gv.egiz.moazs.util.NullCoalesce.coalesce; import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.Error.errorBuilder; @@ -15,6 +18,8 @@ public class RequestStatusResponse extends MsgResponse getResponseAsJAXBElement() { + return factory.createDeliveryRequestStatus(status); + } + @Override public String getAppDeliveryID() { return answer.getAppDeliveryID(); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 0e7b67e..a04a903 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,11 +1,18 @@ -spring: - redis: - host: 172.17.0.2 - port: 6379 +### Logging +logging: + level: + root: WARN + org.springframework: WARN + at.gv: INFO #DEBUG + iaik: INFO #DEBUG + at.gv.egiz.moazs.backend: + SignatureVerifier: DEBUG + LogResponseSink: INFO -## activate cluster mode -# profiles: -# active: cluster +# Default type for java's ssl key/trust store +javax.net.ssl: + keyStoreType: jks + trustStoreType: jks # Configure parameters here or in DeliveryRequest/Config. # Choose a profile in DeliveryRequest/Config/ProfileId. @@ -23,8 +30,12 @@ delivery-request-configuration-profiles: # 0 means indefinitely. receive-timeout: 0 + # Specifies how MoaZS should + msg-response-sinks: + save-response-to-file: false + log-response: true + ssl-profile: - perform-query-person-request: false msg-client: url: https://localhost/zusemsg/services/DeliveryRequest ssl: @@ -43,24 +54,10 @@ delivery-request-configuration-profiles: ## JKS or PKCS12 type: PKCS12 -## If set to false, moa zs ignores an incomplete default DeliveryRequest-configuration -## profile and continues startup. See 'delivery-request-configuration-profiles'. -## Default value: true -# verify-completeness-of-default-delivery-request-configuration: false - -### logging -logging: - level: - root: WARN - org.springframework: WARN - at.gv: INFO #DEBUG - iaik: INFO #DEBUG - at.gv.egiz.moazs.backend.SignatureVerifier: DEBUG - -# default type for java's ssl key/trust store -javax.net.ssl: - keyStoreType: jks - trustStoreType: jks +# If set to false, moa zs ignores an incomplete default DeliveryRequest-configuration +# profile and continues startup. See 'delivery-request-configuration-profiles'. +# Default value: true +verify-completeness-of-default-delivery-request-configuration: true ### moa spss config moa.spss: @@ -74,6 +71,15 @@ moa.spss: configuration: moa-spss/MOASPSSConfiguration.xml default-trustprofile: test-trustprofile +spring: + redis: + host: 172.17.0.2 + port: 6379 + repository: # duration in minutes before repository records are evicted. - expiresAfterWrite: 30 \ No newline at end of file + expiresAfterWrite: 30 + +## activate cluster mode +# profiles: +# active: cluster diff --git a/src/main/resources/mzs/app2mzs.xsd b/src/main/resources/mzs/app2mzs.xsd index 659bbb9..ba99eab 100644 --- a/src/main/resources/mzs/app2mzs.xsd +++ b/src/main/resources/mzs/app2mzs.xsd @@ -98,6 +98,7 @@ + @@ -128,6 +129,13 @@ + + + + + + + diff --git a/src/test/java/at/gv/egiz/moazs/LogResponseSinkTest.java b/src/test/java/at/gv/egiz/moazs/LogResponseSinkTest.java new file mode 100644 index 0000000..9e7c49d --- /dev/null +++ b/src/test/java/at/gv/egiz/moazs/LogResponseSinkTest.java @@ -0,0 +1,78 @@ +package at.gv.egiz.moazs; + +import at.gv.egiz.moazs.backend.LogResponseSink; +import at.gv.egiz.moazs.scheme.Marshaller; +import at.gv.egiz.moazs.scheme.NotificationResponse; +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.slf4j.LoggerFactory; + +import static at.gv.zustellung.msg.xsd.DeliveryNotificationType.deliveryNotificationTypeBuilder; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class LogResponseSinkTest { + + private LogResponseSink logResponseSink; + + @Mock + private Appender appender; + private ch.qos.logback.classic.Logger logger; + + @Before + public void setup() { + logResponseSink = new LogResponseSink(new Marshaller(false)); + logger = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(LogResponseSink.class); + logger.addAppender(appender); + } + + @Test + public void logNotification() { + setLogLevel(Level.INFO); + var appDeliveryID = "log-notification-id"; + var notification = setupNotification(appDeliveryID); + + logResponseSink.log(notification) + .thenRun(expectSubstringSentToAppender(appDeliveryID)); + } + + @Test + public void warnWhenLogDisabled() { + setLogLevel(Level.WARN); + var appDeliveryID = "log-info-disabled"; + var notification = setupNotification(appDeliveryID); + + logResponseSink.log(notification) + .thenRun(expectSubstringSentToAppender("WARN")); + } + + private Runnable expectSubstringSentToAppender(String expectedSubstring) { + return ()-> verify(appender).doAppend(argThat(argument -> { + var message = ((ILoggingEvent) argument).getFormattedMessage(); + return message.contains(expectedSubstring); + })); + } + + private NotificationResponse setupNotification(String appDeliveryID) { + + var notification = deliveryNotificationTypeBuilder() + .withAppDeliveryID(appDeliveryID) + .build(); + + return new NotificationResponse(notification); + + } + + private void setLogLevel(Level level) { + logger.setLevel(level); + } + + +} diff --git a/src/test/java/at/gv/egiz/moazs/SaveResponseToFileSinkTest.java b/src/test/java/at/gv/egiz/moazs/SaveResponseToFileSinkTest.java index 9ddd37e..58efe6a 100644 --- a/src/test/java/at/gv/egiz/moazs/SaveResponseToFileSinkTest.java +++ b/src/test/java/at/gv/egiz/moazs/SaveResponseToFileSinkTest.java @@ -63,7 +63,7 @@ public class SaveResponseToFileSinkTest { var fileContent = "some content"; var status = setupMocks(fileContent); - sink.apply(status) + sink.save(status) .thenRun(() -> assertFilesCreatedAndContentMatches(fileContent)); } -- cgit v1.2.3