diff options
11 files changed, 286 insertions, 49 deletions
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<Void> 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<String> { private final DeliveryRepository repository; private final Consumer<byte[]> signatureVerifier; - private final Function<MsgResponse, CompletableFuture<Void>> sink; + private final SaveResponseToFileSink saveResponseToFileSink; + private final LogResponseSink logResponseSink; @Autowired public MsgResponseBackend(DeliveryRepository repository, Consumer<byte[]> signatureVerifier, - Function<MsgResponse, CompletableFuture<Void>> 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<String> { } + 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<MsgResponse, CompletableFuture<Void>> { +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<MsgResponse, Completable this.isoFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); } - @Override - public CompletableFuture<Void> 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<Void> save(MsgResponse response) { var responseID = response.getResponseID(); @@ -79,6 +84,7 @@ public class SaveResponseToFileSink implements Function<MsgResponse, Completable } private void storeToFile(String path, byte[] content) { + log.trace(SAVE_RESPONSE_MSG, path); try { FileUtils.writeByteArrayToFile(new File(path), content); } catch (IOException e) { diff --git a/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java b/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java index 68f833e..9cdc07a 100644 --- a/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java +++ b/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java @@ -1,10 +1,7 @@ package at.gv.egiz.moazs.preprocess; import at.gv.egiz.moazs.util.StringUtils; -import at.gv.zustellung.app2mzs.xsd.ClientType; -import at.gv.zustellung.app2mzs.xsd.ConfigType; -import at.gv.zustellung.app2mzs.xsd.KeyStoreType; -import at.gv.zustellung.app2mzs.xsd.SSLType; +import at.gv.zustellung.app2mzs.xsd.*; import org.springframework.stereotype.Component; import java.math.BigInteger; @@ -13,6 +10,7 @@ import java.util.Map; import static at.gv.zustellung.app2mzs.xsd.ClientType.clientTypeBuilder; import static at.gv.zustellung.app2mzs.xsd.ConfigType.configTypeBuilder; import static at.gv.zustellung.app2mzs.xsd.KeyStoreType.keyStoreTypeBuilder; +import static at.gv.zustellung.app2mzs.xsd.MsgResponseSinksType.msgResponseSinksTypeBuilder; import static at.gv.zustellung.app2mzs.xsd.SSLType.SSLTypeBuilder; import static java.util.stream.Collectors.toMap; @@ -33,7 +31,9 @@ public class ConfigUtil { public static final String PASSWORD_KEY = "password"; public static final String RECEIVE_TIMEOUT = "receive-timeout"; public static final String CONNECTION_TIMEOUT_KEY = "connection-timeout"; - + public static final String MSG_RESPONSE_SINKS_KEY = "msg-response-sinks"; + public static final String LOG_RESPONSE_KEY = "log-response"; + public static final String SAVE_RESPONSE_TO_FILE_KEY = "save-response-to-file"; /** * Convert a map into a Config object. @@ -53,10 +53,15 @@ public class ConfigUtil { ClientType tnvzClient = tnvzClientParams.isEmpty() ? null : buildClient(tnvzClientParams); + var msgResponseSinksParams = filterMapByPrefix(values, MSG_RESPONSE_SINKS_KEY); + MsgResponseSinksType sinks = msgResponseSinksParams.isEmpty() + ? null : buildMsgResponseSinks(msgResponseSinksParams); + return ConfigType.configTypeBuilder() .withPerformQueryPersonRequest(performQueryPersonRequest) .withMSGClient(msgClient) .withTNVZClient(tnvzClient) + .withMsgResponseSinks(sinks) .build(); } @@ -102,11 +107,8 @@ public class ConfigUtil { KeyStoreType trustStore = trustStoreParams.isEmpty() ? null : buildKeyStore(trustStoreParams); - var trustAll = sslParams.get(TRUST_ALL_KEY) == null - ? null : Boolean.getBoolean(sslParams.get(TRUST_ALL_KEY)); - - var laxHostNameVerification = sslParams.get(LAX_HOSTNAME_VERIFICATION_KEY) == null - ? null : Boolean.getBoolean(sslParams.get(LAX_HOSTNAME_VERIFICATION_KEY)); + var trustAll = booleanOrNull(sslParams.get(TRUST_ALL_KEY)); + var laxHostNameVerification = booleanOrNull(sslParams.get(LAX_HOSTNAME_VERIFICATION_KEY)); return SSLTypeBuilder() .withKeyStore(keyStore) @@ -126,6 +128,22 @@ public class ConfigUtil { .build(); } + private MsgResponseSinksType buildMsgResponseSinks(Map<String, String> 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 <T> The type of the response. + */ public abstract class MsgResponse <T> { protected String id; @@ -20,6 +26,7 @@ public abstract class MsgResponse <T> { } public abstract T getResponse(); + public abstract JAXBElement<T> 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<DeliveryNotificationType> 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); @@ -26,6 +30,11 @@ public class NotificationResponse extends MsgResponse<DeliveryNotificationType> } @Override + public JAXBElement<DeliveryNotificationType> 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<DeliveryRequestStatusType private final DeliveryRequestStatusType status; private final DeliveryAnswerType answer; private static final String ID_SUFFIX = ".RS"; + private static final ObjectFactory factory = new ObjectFactory(); + public RequestStatusResponse(DeliveryRequestStatusType status) { this.status = status; @@ -32,6 +37,11 @@ public class RequestStatusResponse extends MsgResponse<DeliveryRequestStatusType } @Override + public JAXBElement<DeliveryRequestStatusType> 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 @@ <xs:element name="PerformQueryPersonRequest" type="xs:boolean" minOccurs="0" /> <xs:element ref="MSGClient" minOccurs="0" /> <xs:element ref="TNVZClient" minOccurs="0" /> + <xs:element ref="MsgResponseSinks" minOccurs="0" /> </xs:sequence> </xs:complexType> <xs:element name="MSGClient" type="ClientType" /> @@ -128,6 +129,13 @@ <xs:element name="FileType" type="xs:string" minOccurs="0"/> </xs:sequence> </xs:complexType> + <xs:element name="MsgResponseSinks" type="MsgResponseSinksType"/> + <xs:complexType name="MsgResponseSinksType"> + <xs:sequence> + <xs:element name="SafeResponseToFile" type="xs:boolean" minOccurs="0" /> + <xs:element name="LogResponse" type="xs:boolean" minOccurs="0" /> + </xs:sequence> + </xs:complexType> <xs:element name="DeliveryResponse" type="DeliveryResponseType"/> <xs:complexType name="DeliveryResponseType"> <xs:choice> 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 = "<xml>some content</xml>"; var status = setupMocks(fileContent); - sink.apply(status) + sink.save(status) .thenRun(() -> assertFilesCreatedAndContentMatches(fileContent)); } |