From 78f0715d86a055aed11138df5f66b0794e72326a Mon Sep 17 00:00:00 2001 From: Christof Rabensteiner Date: Wed, 3 Jul 2019 15:27:14 +0200 Subject: Refactor: Restructure Project - Move components that depend on the service contracts into scheme package. - Move cxf related components into util package. - Rename SameThread to SingleThreaded. --- .../at/gv/egiz/moazs/config/MzsServiceConfig.java | 2 +- .../at/gv/egiz/moazs/config/PreprocessConfig.java | 4 +- .../java/at/gv/egiz/moazs/msg/ClientFactory.java | 113 ---------- ...StoreSOAPBodyBinaryInRepositoryInterceptor.java | 65 ------ src/main/java/at/gv/egiz/moazs/mzs/MzsClient.java | 13 -- src/main/java/at/gv/egiz/moazs/mzs/MzsService.java | 105 ---------- .../java/at/gv/egiz/moazs/mzs/MzsValidator.java | 74 ------- .../moazs/pipeline/SameThreadDeliveryPipeline.java | 146 ------------- .../pipeline/SingleThreadedDeliveryPipeline.java | 146 +++++++++++++ .../moazs/preprocess/ConfigProfileGenerator.java | 10 +- .../moazs/preprocess/DeliveryRequestAugmenter.java | 6 +- .../java/at/gv/egiz/moazs/scheme/MzsClient.java | 13 ++ .../moazs/scheme/MzsDeliveryRequestValidator.java | 74 +++++++ .../java/at/gv/egiz/moazs/scheme/MzsService.java | 104 +++++++++ .../java/at/gv/egiz/moazs/scheme/TnvzHelper.java | 232 +++++++++++++++++++++ .../java/at/gv/egiz/moazs/tnvz/TnvzHelper.java | 232 --------------------- .../java/at/gv/egiz/moazs/util/ClientFactory.java | 111 ++++++++++ ...StoreSOAPBodyBinaryInRepositoryInterceptor.java | 64 ++++++ 18 files changed, 755 insertions(+), 759 deletions(-) delete mode 100644 src/main/java/at/gv/egiz/moazs/msg/ClientFactory.java delete mode 100644 src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java delete mode 100644 src/main/java/at/gv/egiz/moazs/mzs/MzsClient.java delete mode 100644 src/main/java/at/gv/egiz/moazs/mzs/MzsService.java delete mode 100644 src/main/java/at/gv/egiz/moazs/mzs/MzsValidator.java delete mode 100644 src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java create mode 100644 src/main/java/at/gv/egiz/moazs/pipeline/SingleThreadedDeliveryPipeline.java create mode 100644 src/main/java/at/gv/egiz/moazs/scheme/MzsClient.java create mode 100644 src/main/java/at/gv/egiz/moazs/scheme/MzsDeliveryRequestValidator.java create mode 100644 src/main/java/at/gv/egiz/moazs/scheme/MzsService.java create mode 100644 src/main/java/at/gv/egiz/moazs/scheme/TnvzHelper.java delete mode 100644 src/main/java/at/gv/egiz/moazs/tnvz/TnvzHelper.java create mode 100644 src/main/java/at/gv/egiz/moazs/util/ClientFactory.java create mode 100644 src/main/java/at/gv/egiz/moazs/util/StoreSOAPBodyBinaryInRepositoryInterceptor.java (limited to 'src/main') diff --git a/src/main/java/at/gv/egiz/moazs/config/MzsServiceConfig.java b/src/main/java/at/gv/egiz/moazs/config/MzsServiceConfig.java index 713c616..9fd14eb 100644 --- a/src/main/java/at/gv/egiz/moazs/config/MzsServiceConfig.java +++ b/src/main/java/at/gv/egiz/moazs/config/MzsServiceConfig.java @@ -1,6 +1,6 @@ package at.gv.egiz.moazs.config; -import at.gv.egiz.moazs.mzs.MzsService; +import at.gv.egiz.moazs.scheme.MzsService; import at.gv.zustellung.app2mzs.xsd.App2Mzs; import org.apache.cxf.Bus; import org.apache.cxf.jaxws.EndpointImpl; diff --git a/src/main/java/at/gv/egiz/moazs/config/PreprocessConfig.java b/src/main/java/at/gv/egiz/moazs/config/PreprocessConfig.java index b90b6a3..b24db28 100644 --- a/src/main/java/at/gv/egiz/moazs/config/PreprocessConfig.java +++ b/src/main/java/at/gv/egiz/moazs/config/PreprocessConfig.java @@ -1,6 +1,6 @@ package at.gv.egiz.moazs.config; -import at.gv.egiz.moazs.mzs.MzsValidator; +import at.gv.egiz.moazs.scheme.MzsDeliveryRequestValidator; import at.gv.egiz.moazs.preprocess.*; import at.gv.zustellung.app2mzs.xsd.ConfigType; import org.springframework.beans.factory.annotation.Autowired; @@ -28,7 +28,7 @@ public class PreprocessConfig { @Autowired public ConfigProfileGenerator configProfileGenerator( SpringPropertiesFacade properties, - MzsValidator validator, + MzsDeliveryRequestValidator validator, ConfigUtil util) { return configProfileGeneratorBuilder() diff --git a/src/main/java/at/gv/egiz/moazs/msg/ClientFactory.java b/src/main/java/at/gv/egiz/moazs/msg/ClientFactory.java deleted file mode 100644 index c2ad4a7..0000000 --- a/src/main/java/at/gv/egiz/moazs/msg/ClientFactory.java +++ /dev/null @@ -1,113 +0,0 @@ -package at.gv.egiz.moazs.msg; - -import at.gv.egiz.moazs.util.FileUtils; -import at.gv.egiz.moazs.util.SSLContextCreator; -import at.gv.zustellung.app2mzs.xsd.ClientType; -import at.gv.zustellung.app2mzs.xsd.KeyStoreType; -import at.gv.zustellung.app2mzs.xsd.SSLType; -import javax.xml.ws.soap.SOAPBinding; -import org.apache.cxf.configuration.jsse.TLSClientParameters; -import org.apache.cxf.endpoint.Client; -import org.apache.cxf.frontend.ClientProxy; -import org.apache.cxf.jaxws.JaxWsClientFactoryBean; -import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; -import org.apache.cxf.transport.http.HTTPConduit; -import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.annotation.Nullable; -import javax.net.ssl.SSLContext; -import javax.xml.ws.BindingProvider; - -import static at.gv.zustellung.app2mzs.xsd.KeyStoreType.keyStoreTypeBuilder; - -@Component -public class ClientFactory { - - private static final Logger log = LoggerFactory.getLogger(ClientFactory.class); - - private final StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor; - private final SSLContextCreator sslContextCreator; - private final FileUtils fileUtils; - - @Autowired - public ClientFactory(StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor, - SSLContextCreator creator, - FileUtils fileUtils) { - this.storeResponseInterceptor = storeResponseInterceptor; - this.sslContextCreator = creator; - this.fileUtils = fileUtils; - } - - /** - * Creates a client that communicates with a soap service. - * - * @param params for the client, such as service url and ssl parameters. - * @return the client - */ - public T create(ClientType params, Class clazz) { - - var factory = new JaxWsClientFactoryBean(); - factory.setServiceClass(clazz); - factory.setAddress(params.getURL()); - factory.getInInterceptors().add(storeResponseInterceptor); - - var proxy = new JaxWsProxyFactoryBean(factory).create(); - Client client = ClientProxy.getClient(proxy); - HTTPConduit http = (HTTPConduit) client.getConduit(); - - var bindingProvider = (BindingProvider) proxy; - SOAPBinding binding = (SOAPBinding) bindingProvider.getBinding(); - binding.setMTOMEnabled(true); - - var httpClientPolicy = new HTTPClientPolicy(); - httpClientPolicy.setConnectionTimeout(params.getConnectionTimeout().longValueExact()); - httpClientPolicy.setReceiveTimeout(params.getReceiveTimeout().longValueExact()); - http.setClient(httpClientPolicy); - - if (params.getURL().startsWith("https")) { - TLSClientParameters tlsParams = setupTLSParams(params.getSSL()); - http.setTlsClientParameters(tlsParams); - log.info("SSLContext initialized. "); - } - - return ((T)proxy); - } - - private TLSClientParameters setupTLSParams(SSLType ssl) { - - var tlsParams = new TLSClientParameters(); - var keystore = resolveKeyStorePath(ssl.getKeyStore()); - - SSLContext sslContext; - if (ssl.isTrustAll()) { - sslContext = sslContextCreator.createUnsafeSSLContext(keystore); - } else { - var truststore = resolveKeyStorePath(ssl.getTrustStore()); - sslContext = sslContextCreator.createSSLContext(keystore, truststore); - } - tlsParams.setSSLSocketFactory(sslContext.getSocketFactory()); - - if (ssl.isLaxHostNameVerification()) { - tlsParams.setDisableCNCheck(true); - } - - return tlsParams; - } - - private KeyStoreType resolveKeyStorePath(@Nullable KeyStoreType store) { - - if (store == null) return null; - - var resolvedURI = "file:" + fileUtils.determinePath(store.getFileName()); - log.trace("Resolved key store path from {} to {}.", store.getFileName(), resolvedURI); - - return keyStoreTypeBuilder(store) - .withFileName(resolvedURI) - .build(); - } - -} diff --git a/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java b/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java deleted file mode 100644 index ac3acc6..0000000 --- a/src/main/java/at/gv/egiz/moazs/msg/StoreSOAPBodyBinaryInRepositoryInterceptor.java +++ /dev/null @@ -1,65 +0,0 @@ -package at.gv.egiz.moazs.msg; - -import at.gv.egiz.moazs.repository.DeliveryRepository; -import at.gv.egiz.moazs.scheme.SOAPUtils; -import at.gv.egiz.moazs.util.CXFMessageUtils; -import org.apache.cxf.message.Message; -import org.apache.cxf.phase.AbstractPhaseInterceptor; -import org.apache.cxf.phase.Phase; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.w3c.dom.Element; -import org.xml.sax.SAXException; - -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import static at.gv.egiz.moazs.MoaZSException.moaZSException; - -@Component -public class StoreSOAPBodyBinaryInRepositoryInterceptor extends AbstractPhaseInterceptor { - - private static final Logger log = LoggerFactory.getLogger(StoreSOAPBodyBinaryInRepositoryInterceptor.class); - - private final CXFMessageUtils messageUtils; - private final SOAPUtils soapUtils; - private final DeliveryRepository repository; - - @Autowired - public StoreSOAPBodyBinaryInRepositoryInterceptor(CXFMessageUtils extractor, SOAPUtils soapUtils, - DeliveryRepository repository) { - super(Phase.RECEIVE); - this.messageUtils = extractor; - this.soapUtils = soapUtils; - this.repository = repository; - } - - public void handleMessage(Message message) { - - try { - byte[] content = messageUtils.copyContent(message); - - if(log.isTraceEnabled()) { - log.trace("Interceptor received this SOAP message: {}. ", new String(content, StandardCharsets.UTF_8)); - } - - if(content.length > 0) { - Element document = soapUtils.toDOM(content); - byte[] status = soapUtils.unwrapSoapEnvelope(document); - String appDeliveryID = soapUtils.getAppDeliveryIDFrom(document); - repository.addSignedDeliveryRequestStatus(status, appDeliveryID); - - if(log.isTraceEnabled()) { - log.trace("DeliveryRequestStatus with AppDeliveryID={} unwrapped and stored: {}. ", - appDeliveryID, new String(status, StandardCharsets.UTF_8)); - } - } - } catch (ParserConfigurationException | SAXException | IOException | NullPointerException e) { - throw moaZSException("Could not extract signed data from message.", e); - } - } - -} diff --git a/src/main/java/at/gv/egiz/moazs/mzs/MzsClient.java b/src/main/java/at/gv/egiz/moazs/mzs/MzsClient.java deleted file mode 100644 index 980dce1..0000000 --- a/src/main/java/at/gv/egiz/moazs/mzs/MzsClient.java +++ /dev/null @@ -1,13 +0,0 @@ -package at.gv.egiz.moazs.mzs; - -import at.gv.zustellung.app2mzs.xsd.DeliveryResponseType; -import org.springframework.stereotype.Component; - -@Component -public class MzsClient { - - public void sendNotification(DeliveryResponseType responseType) { - throw new UnsupportedOperationException("Not implemented."); - } - -} diff --git a/src/main/java/at/gv/egiz/moazs/mzs/MzsService.java b/src/main/java/at/gv/egiz/moazs/mzs/MzsService.java deleted file mode 100644 index f4ecfdb..0000000 --- a/src/main/java/at/gv/egiz/moazs/mzs/MzsService.java +++ /dev/null @@ -1,105 +0,0 @@ -package at.gv.egiz.moazs.mzs; - -import at.gv.egiz.moazs.pipeline.DeliveryPipeline; -import at.gv.egiz.moazs.preprocess.DeliveryRequestAugmenter; -import at.gv.egiz.moazs.repository.DeliveryRepository; -import at.gv.egiz.moazs.scheme.Msg2MzsConverter; -import at.gv.zustellung.app2mzs.xsd.App2MzsPortType; -import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; -import at.gv.zustellung.app2mzs.xsd.DeliveryResponseType; -import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; -import org.apache.cxf.annotations.SchemaValidation; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import javax.jws.WebParam; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import static at.gv.egiz.moazs.MoaZSException.moaZSException; -import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; -import static at.gv.zustellung.app2mzs.xsd.PartialSuccessType.partialSuccessTypeBuilder; -import static java.util.concurrent.CompletableFuture.supplyAsync; - -//todo : validate Schema in both directions. -@Service -@SchemaValidation(type = SchemaValidation.SchemaValidationType.IN) -public class MzsService implements App2MzsPortType { - - private static final Logger logger = LoggerFactory.getLogger(MzsService.class); - - //TODO move timeout and namespaces to config - private static final int TIMEOUT_FOR_ANWSER = 10; - - private final DeliveryRepository repository; - private final DeliveryPipeline pipeline; - private final MzsClient appClient; - private final DeliveryRequestAugmenter augmenter; - private final Msg2MzsConverter converter; - - @Autowired - public MzsService(DeliveryRepository repository, DeliveryPipeline pipeline, MzsClient appClient, - DeliveryRequestAugmenter augmenter, Msg2MzsConverter converter) { - this.repository = repository; - this.pipeline = pipeline; - this.appClient = appClient; - this.augmenter = augmenter; - this.converter = converter; - } - - @Override - public DeliveryResponseType app2Mzs( - @WebParam(partName = "DeliveryRequest", - name = "DeliveryRequest") - DeliveryRequestType deliveryRequest) { - - var appDeliveryID = deliveryRequest.getMetaData().getAppDeliveryID(); - - var future = supplyAsync(() -> augmenter.augment(deliveryRequest)) - .thenApply(this::process) - .thenApply(status -> converter.convert(status, repository.getSignedDeliveryRequestStatus(appDeliveryID))); - - try { - return future.get(TIMEOUT_FOR_ANWSER, TimeUnit.SECONDS); - } catch (TimeoutException e) { - logger.info("Answer Timed Out", e); - - future.thenAccept(appClient::sendNotification); - return generatePartialSuccessResponse(appDeliveryID); - - } catch (Exception e) { - throw moaZSExceptionBuilder("An error occurred while processing DeliveryRequest " + - "with AppDeliveryID=%s.", appDeliveryID) - .withCause(e) - .build(); - } - - } - - private DeliveryRequestStatusType process(DeliveryRequestType deliveryRequest) { - - var appDeliveryID = deliveryRequest.getMetaData().getAppDeliveryID(); - logger.info("Receive request with appDeliveryID = {}.", appDeliveryID); - - repository.add(deliveryRequest); - pipeline.processRequest(appDeliveryID); - - return repository.getDeliveryRequestStatus(appDeliveryID) - .orElseThrow(() -> moaZSException("Could not get a response for AppDeliveryID=%s.", appDeliveryID)); - - } - - private DeliveryResponseType generatePartialSuccessResponse(String appDeliveryId) { - - var partial = partialSuccessTypeBuilder() - .withAppDeliveryID(appDeliveryId) - .build(); - - return DeliveryResponseType.deliveryResponseTypeBuilder() - .withPartialSuccess(partial) - .build(); - } - -} diff --git a/src/main/java/at/gv/egiz/moazs/mzs/MzsValidator.java b/src/main/java/at/gv/egiz/moazs/mzs/MzsValidator.java deleted file mode 100644 index 26ea7dc..0000000 --- a/src/main/java/at/gv/egiz/moazs/mzs/MzsValidator.java +++ /dev/null @@ -1,74 +0,0 @@ -package at.gv.egiz.moazs.mzs; - -import at.gv.zustellung.app2mzs.xsd.ClientType; -import at.gv.zustellung.app2mzs.xsd.ConfigType; -import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; -import at.gv.zustellung.app2mzs.xsd.KeyStoreType; -import org.springframework.lang.Nullable; -import org.springframework.stereotype.Component; - -@Component -public class MzsValidator { - - /** - * Checks if the mandatory fields that are needed to send a tnvz:QueryPersonRequest are present. - * @param request - * @return true if mandatory fields are present. - */ - public boolean isTnvzComplete(DeliveryRequestType request) { - return !request.getConfig().isPerformQueryPersonRequest() || - (request.getTnvzMetaData() != null - && request.getSender().getCorporateBody() != null); - - - } - - /** - * Check if all mandatory fields of configuration are present. - * - * @param profile - * @return true if all mandatory fields are present. - */ - public boolean isConfigProfileComplete(@Nullable ConfigType profile) { - return profile != null - && profile.isPerformQueryPersonRequest() != null - && isTVNZClientConfigured(profile.getTNVZClient(), profile.isPerformQueryPersonRequest()) - && isMSGClientConfigured(profile.getMSGClient()); - } - - private boolean isTVNZClientConfigured(ClientType tnvzClient, Boolean isPerformQueryPersonRequest) { - return !isPerformQueryPersonRequest || (tnvzClient != null - && tnvzClient.getURL() != null - && tnvzClient.getReceiveTimeout() != null - && tnvzClient.getConnectionTimeout() != null - && isSSLConfigured(tnvzClient)); - } - - private boolean isMSGClientConfigured(ClientType msgClientParams) { - return msgClientParams != null - && msgClientParams.getURL() != null - && isSSLConfigured(msgClientParams) - && msgClientParams.getReceiveTimeout() != null - && msgClientParams.getConnectionTimeout() != null; - } - - private boolean isSSLConfigured(ClientType clientParams) { - return !clientParams.getURL().startsWith("https") || (clientParams.getSSL() != null - && clientParams.getSSL().isTrustAll() != null - && clientParams.getSSL().isLaxHostNameVerification() != null - && isKeyStoreConfigured(clientParams.getSSL().getKeyStore()) - && isTrustStoreConfigured(clientParams.getSSL().getTrustStore())); - } - - private boolean isKeyStoreConfigured(KeyStoreType keyStore) { - return keyStore == null || (keyStore.getPassword() != null - && keyStore.getFileType() != null - && keyStore.getFileName() != null); - } - - private boolean isTrustStoreConfigured(KeyStoreType trustStore) { - return trustStore == null || (trustStore.getPassword() != null - && "JKS".equals(trustStore.getFileType()) - && trustStore.getFileName() != null); - } -} diff --git a/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java b/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java deleted file mode 100644 index ecf580d..0000000 --- a/src/main/java/at/gv/egiz/moazs/pipeline/SameThreadDeliveryPipeline.java +++ /dev/null @@ -1,146 +0,0 @@ -package at.gv.egiz.moazs.pipeline; - - -import at.gv.egiz.moazs.MoaZSException; -import at.gv.egiz.moazs.msg.ClientFactory; -import at.gv.egiz.moazs.repository.DeliveryRepository; -import at.gv.egiz.moazs.scheme.Mzs2MsgConverter; -import at.gv.egiz.moazs.scheme.NameSpace; -import at.gv.egiz.moazs.tnvz.TnvzHelper; -import at.gv.egiz.moazs.verify.SignatureVerifier; -import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; -import at.gv.zustellung.msg.xsd.App2ZusePort; -import at.gv.zustellung.msg.xsd.DeliveryAnswerType; -import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; -import at.gv.zustellung.tnvz.xsd.TNVZServicePort; -import org.apache.log4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Profile; -import org.springframework.stereotype.Component; - -import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; -import static at.gv.egiz.moazs.util.NullCoalesce.coalesce; -import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.Error.errorBuilder; -import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.deliveryRequestStatusTypeBuilder; -import static at.gv.zustellung.msg.xsd.ErrorInfoType.errorInfoTypeBuilder; -import static java.lang.String.format; - -@Component -@Profile("!cluster") -public class SameThreadDeliveryPipeline implements DeliveryPipeline { - - private static final Logger log = Logger.getLogger(SameThreadDeliveryPipeline.class); - - private final DeliveryRepository repository; - private final TnvzHelper tnvzHelper; - private final Mzs2MsgConverter converter; - private final ClientFactory clientFactory; - private final SignatureVerifier verifier; - - @Autowired - public SameThreadDeliveryPipeline(DeliveryRepository repository, - TnvzHelper tnvzHelper, - Mzs2MsgConverter converter, - ClientFactory clientFactory, - SignatureVerifier verifier - ) { - this.repository = repository; - this.tnvzHelper = tnvzHelper; - this.converter = converter; - this.clientFactory = clientFactory; - this.verifier = verifier; - } - - @Override - public void processRequest(String appDeliveryId) { - - var exceptionBuilder = moaZSExceptionBuilder(); - - try { - var mzsRequest = repository.getDeliveryRequest(appDeliveryId).orElseThrow(); - exceptionBuilder.withMzsRequest(mzsRequest); - - at.gv.zustellung.msg.xsd.DeliveryRequestType msgRequest = buildMsgRequest(mzsRequest, exceptionBuilder); - exceptionBuilder.withMsgRequest(msgRequest); - - var msgClientParams = mzsRequest.getConfig().getMSGClient(); - App2ZusePort client = clientFactory.create(msgClientParams, App2ZusePort.class); - var status = client.delivery(msgRequest); - exceptionBuilder.withMsgResult(status); - - verifySignedStatus(appDeliveryId, exceptionBuilder); - repository.add(status); - - } catch (MoaZSException exception) { - - log.error(format("An error occured while processing the DeliveryRequest with AppDeliveryID=%s. ", - appDeliveryId), exception); - - var errorStatus = generateErrorStatus(exception, appDeliveryId); - repository.add(errorStatus); - } - } - - private at.gv.zustellung.msg.xsd.DeliveryRequestType buildMsgRequest(DeliveryRequestType mzsRequest, - MoaZSException.Builder exceptionBuilder) { - if (mzsRequest.getConfig().isPerformQueryPersonRequest()) { - var tnvzClientParams = mzsRequest.getConfig().getTNVZClient(); - TNVZServicePort tvnzPort = clientFactory.create(tnvzClientParams, TNVZServicePort.class); - var identification = tnvzHelper.performQueryPersonRequest(mzsRequest, tvnzPort, exceptionBuilder); - return converter.convert(mzsRequest, identification); - } else { - return converter.convert(mzsRequest); - } - } - - private void verifySignedStatus(String appDeliveryId, MoaZSException.Builder exceptionBuilder) throws MoaZSException { - - try { - var signedStatus = repository.getSignedDeliveryRequestStatus(appDeliveryId).get(); - verifier.verify(signedStatus); - } catch (Exception ex) { - throw exceptionBuilder.withMessage(format("Signature of DeliveryRequestStatus with AppDeliveryID=%s " + - " is not valid.", appDeliveryId)) - .withErrorCode(MoaZSException.ERROR_MOASP_SIGNATURE_INVALID) - .withCause(ex) - .build(); - } - } - - private DeliveryRequestStatusType generateErrorStatus(MoaZSException exception, String appDeliveryId) { - - var infoBuilder = errorInfoTypeBuilder() - .withText(exception.getMessage()) - .withCode(exception.getErrorCode()); - - var errorBuilder = errorBuilder() - .withErrorInfo(infoBuilder.build()) - .withAppDeliveryID(appDeliveryId); - - if (exception.getMzsRequest() != null) { - errorBuilder.withDeliverySystem(exception.getMzsRequest().getConfig().getMSGClient().getURL()); - } - - if (exception.getTnvzResult() != null && exception.getTnvzResult().getError() != null) { - errorBuilder.withPreAdviceNoteSent(exception.getTnvzResult().getError().getPreAdviceNoteSent()); - } - - if (exception.getMsgResult() != null) { - var answer = getAnswerFromResult(exception.getMsgResult()); - errorBuilder.withGZ(answer.getGZ()); - errorBuilder.withZSDeliveryID(answer.getZSDeliveryID()); - } - - return deliveryRequestStatusTypeBuilder() - .withError(errorBuilder.build()) - .withVersion(NameSpace.MSG_VERSION) - .build(); - - } - - private DeliveryAnswerType getAnswerFromResult(DeliveryRequestStatusType msgResult) { - return coalesce(msgResult.getSuccess(), msgResult.getPartialSuccess(), msgResult.getError()).get(); - } - - -} diff --git a/src/main/java/at/gv/egiz/moazs/pipeline/SingleThreadedDeliveryPipeline.java b/src/main/java/at/gv/egiz/moazs/pipeline/SingleThreadedDeliveryPipeline.java new file mode 100644 index 0000000..783df32 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/pipeline/SingleThreadedDeliveryPipeline.java @@ -0,0 +1,146 @@ +package at.gv.egiz.moazs.pipeline; + + +import at.gv.egiz.moazs.MoaZSException; +import at.gv.egiz.moazs.util.ClientFactory; +import at.gv.egiz.moazs.repository.DeliveryRepository; +import at.gv.egiz.moazs.scheme.Mzs2MsgConverter; +import at.gv.egiz.moazs.scheme.NameSpace; +import at.gv.egiz.moazs.scheme.TnvzHelper; +import at.gv.egiz.moazs.verify.SignatureVerifier; +import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; +import at.gv.zustellung.msg.xsd.App2ZusePort; +import at.gv.zustellung.msg.xsd.DeliveryAnswerType; +import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; +import at.gv.zustellung.tnvz.xsd.TNVZServicePort; +import org.apache.log4j.Logger; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; + +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; +import static at.gv.egiz.moazs.util.NullCoalesce.coalesce; +import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.Error.errorBuilder; +import static at.gv.zustellung.msg.xsd.DeliveryRequestStatusType.deliveryRequestStatusTypeBuilder; +import static at.gv.zustellung.msg.xsd.ErrorInfoType.errorInfoTypeBuilder; +import static java.lang.String.format; + +@Component +@Profile("!cluster") +public class SingleThreadedDeliveryPipeline implements DeliveryPipeline { + + private static final Logger log = Logger.getLogger(SingleThreadedDeliveryPipeline.class); + + private final DeliveryRepository repository; + private final TnvzHelper tnvzHelper; + private final Mzs2MsgConverter converter; + private final ClientFactory clientFactory; + private final SignatureVerifier verifier; + + @Autowired + public SingleThreadedDeliveryPipeline(DeliveryRepository repository, + TnvzHelper tnvzHelper, + Mzs2MsgConverter converter, + ClientFactory clientFactory, + SignatureVerifier verifier + ) { + this.repository = repository; + this.tnvzHelper = tnvzHelper; + this.converter = converter; + this.clientFactory = clientFactory; + this.verifier = verifier; + } + + @Override + public void processRequest(String appDeliveryId) { + + var exceptionBuilder = moaZSExceptionBuilder(); + + try { + var mzsRequest = repository.getDeliveryRequest(appDeliveryId).orElseThrow(); + exceptionBuilder.withMzsRequest(mzsRequest); + + at.gv.zustellung.msg.xsd.DeliveryRequestType msgRequest = buildMsgRequest(mzsRequest, exceptionBuilder); + exceptionBuilder.withMsgRequest(msgRequest); + + var msgClientParams = mzsRequest.getConfig().getMSGClient(); + App2ZusePort client = clientFactory.create(msgClientParams, App2ZusePort.class); + var status = client.delivery(msgRequest); + exceptionBuilder.withMsgResult(status); + + verifySignedStatus(appDeliveryId, exceptionBuilder); + repository.add(status); + + } catch (MoaZSException exception) { + + log.error(format("An error occured while processing the DeliveryRequest with AppDeliveryID=%s. ", + appDeliveryId), exception); + + var errorStatus = generateErrorStatus(exception, appDeliveryId); + repository.add(errorStatus); + } + } + + private at.gv.zustellung.msg.xsd.DeliveryRequestType buildMsgRequest(DeliveryRequestType mzsRequest, + MoaZSException.Builder exceptionBuilder) { + if (mzsRequest.getConfig().isPerformQueryPersonRequest()) { + var tnvzClientParams = mzsRequest.getConfig().getTNVZClient(); + TNVZServicePort tvnzPort = clientFactory.create(tnvzClientParams, TNVZServicePort.class); + var identification = tnvzHelper.performQueryPersonRequest(mzsRequest, tvnzPort, exceptionBuilder); + return converter.convert(mzsRequest, identification); + } else { + return converter.convert(mzsRequest); + } + } + + private void verifySignedStatus(String appDeliveryId, MoaZSException.Builder exceptionBuilder) throws MoaZSException { + + try { + var signedStatus = repository.getSignedDeliveryRequestStatus(appDeliveryId).get(); + verifier.verify(signedStatus); + } catch (Exception ex) { + throw exceptionBuilder.withMessage(format("Signature of DeliveryRequestStatus with AppDeliveryID=%s " + + " is not valid.", appDeliveryId)) + .withErrorCode(MoaZSException.ERROR_MOASP_SIGNATURE_INVALID) + .withCause(ex) + .build(); + } + } + + private DeliveryRequestStatusType generateErrorStatus(MoaZSException exception, String appDeliveryId) { + + var infoBuilder = errorInfoTypeBuilder() + .withText(exception.getMessage()) + .withCode(exception.getErrorCode()); + + var errorBuilder = errorBuilder() + .withErrorInfo(infoBuilder.build()) + .withAppDeliveryID(appDeliveryId); + + if (exception.getMzsRequest() != null) { + errorBuilder.withDeliverySystem(exception.getMzsRequest().getConfig().getMSGClient().getURL()); + } + + if (exception.getTnvzResult() != null && exception.getTnvzResult().getError() != null) { + errorBuilder.withPreAdviceNoteSent(exception.getTnvzResult().getError().getPreAdviceNoteSent()); + } + + if (exception.getMsgResult() != null) { + var answer = getAnswerFromResult(exception.getMsgResult()); + errorBuilder.withGZ(answer.getGZ()); + errorBuilder.withZSDeliveryID(answer.getZSDeliveryID()); + } + + return deliveryRequestStatusTypeBuilder() + .withError(errorBuilder.build()) + .withVersion(NameSpace.MSG_VERSION) + .build(); + + } + + private DeliveryAnswerType getAnswerFromResult(DeliveryRequestStatusType msgResult) { + return coalesce(msgResult.getSuccess(), msgResult.getPartialSuccess(), msgResult.getError()).get(); + } + + +} diff --git a/src/main/java/at/gv/egiz/moazs/preprocess/ConfigProfileGenerator.java b/src/main/java/at/gv/egiz/moazs/preprocess/ConfigProfileGenerator.java index c2f2415..d651570 100644 --- a/src/main/java/at/gv/egiz/moazs/preprocess/ConfigProfileGenerator.java +++ b/src/main/java/at/gv/egiz/moazs/preprocess/ConfigProfileGenerator.java @@ -1,7 +1,7 @@ package at.gv.egiz.moazs.preprocess; import at.gv.egiz.moazs.MoaZSException; -import at.gv.egiz.moazs.mzs.MzsValidator; +import at.gv.egiz.moazs.scheme.MzsDeliveryRequestValidator; import at.gv.egiz.moazs.util.StringUtils; import at.gv.zustellung.app2mzs.xsd.ConfigType; import org.slf4j.Logger; @@ -30,7 +30,7 @@ public class ConfigProfileGenerator { private final SpringPropertiesFacade properties; private final ConfigUtil util; - private final MzsValidator validator; + private final MzsDeliveryRequestValidator validator; private final boolean verifyCompletenessOfDefaultConfiguration; private final String profilePrefix; private final String defaultConfigKey; @@ -42,7 +42,7 @@ public class ConfigProfileGenerator { private ConfigProfileGenerator( SpringPropertiesFacade properties, ConfigUtil util, - MzsValidator validator, boolean verifyCompletenessOfDefaultConfiguration, + MzsDeliveryRequestValidator validator, boolean verifyCompletenessOfDefaultConfiguration, String profilePrefix, String defaultConfigKey) { this.util = util; @@ -113,7 +113,7 @@ public class ConfigProfileGenerator { public static class ConfigProfileGeneratorBuilder { private SpringPropertiesFacade properties; private ConfigUtil util; - private MzsValidator validator; + private MzsDeliveryRequestValidator validator; private boolean verify = true; private String profilePrefix = "delivery-request-configuration-profiles"; private String defaultConfigKey = "default"; @@ -128,7 +128,7 @@ public class ConfigProfileGenerator { return this; } - public ConfigProfileGeneratorBuilder withValidator(MzsValidator validator) { + public ConfigProfileGeneratorBuilder withValidator(MzsDeliveryRequestValidator validator) { this.validator = validator; return this; } diff --git a/src/main/java/at/gv/egiz/moazs/preprocess/DeliveryRequestAugmenter.java b/src/main/java/at/gv/egiz/moazs/preprocess/DeliveryRequestAugmenter.java index 37dbdc5..783536c 100644 --- a/src/main/java/at/gv/egiz/moazs/preprocess/DeliveryRequestAugmenter.java +++ b/src/main/java/at/gv/egiz/moazs/preprocess/DeliveryRequestAugmenter.java @@ -1,6 +1,6 @@ package at.gv.egiz.moazs.preprocess; -import at.gv.egiz.moazs.mzs.MzsValidator; +import at.gv.egiz.moazs.scheme.MzsDeliveryRequestValidator; import at.gv.zustellung.app2mzs.xsd.ConfigType; import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; import org.springframework.beans.factory.annotation.Autowired; @@ -16,7 +16,7 @@ public class DeliveryRequestAugmenter { private final ConfigUtil util; private final Map configs; - private final MzsValidator validator; + private final MzsDeliveryRequestValidator validator; private static final String INCOMPLETE_TNVZ_ERROR_MESSAGE = "mzs:DeliveryRequest is incomplete because mandatory " + "fields for sending a tnvz:QueryPersonRequest are missing."; @@ -27,7 +27,7 @@ public class DeliveryRequestAugmenter { "configuration is incomplete."; @Autowired - public DeliveryRequestAugmenter(Map deliveryRequestConfigs, ConfigUtil util, MzsValidator validator) { + public DeliveryRequestAugmenter(Map deliveryRequestConfigs, ConfigUtil util, MzsDeliveryRequestValidator validator) { this.configs = deliveryRequestConfigs; this.util = util; this.validator = validator; diff --git a/src/main/java/at/gv/egiz/moazs/scheme/MzsClient.java b/src/main/java/at/gv/egiz/moazs/scheme/MzsClient.java new file mode 100644 index 0000000..98e0bc8 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/scheme/MzsClient.java @@ -0,0 +1,13 @@ +package at.gv.egiz.moazs.scheme; + +import at.gv.zustellung.app2mzs.xsd.DeliveryResponseType; +import org.springframework.stereotype.Component; + +@Component +public class MzsClient { + + public void sendNotification(DeliveryResponseType responseType) { + throw new UnsupportedOperationException("Not implemented."); + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/scheme/MzsDeliveryRequestValidator.java b/src/main/java/at/gv/egiz/moazs/scheme/MzsDeliveryRequestValidator.java new file mode 100644 index 0000000..03f2664 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/scheme/MzsDeliveryRequestValidator.java @@ -0,0 +1,74 @@ +package at.gv.egiz.moazs.scheme; + +import at.gv.zustellung.app2mzs.xsd.ClientType; +import at.gv.zustellung.app2mzs.xsd.ConfigType; +import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; +import at.gv.zustellung.app2mzs.xsd.KeyStoreType; +import org.springframework.lang.Nullable; +import org.springframework.stereotype.Component; + +@Component +public class MzsDeliveryRequestValidator { + + /** + * Checks if the mandatory fields that are needed to send a tnvz:QueryPersonRequest are present. + * @param request + * @return true if mandatory fields are present. + */ + public boolean isTnvzComplete(DeliveryRequestType request) { + return !request.getConfig().isPerformQueryPersonRequest() || + (request.getTnvzMetaData() != null + && request.getSender().getCorporateBody() != null); + + + } + + /** + * Check if all mandatory fields of configuration are present. + * + * @param profile + * @return true if all mandatory fields are present. + */ + public boolean isConfigProfileComplete(@Nullable ConfigType profile) { + return profile != null + && profile.isPerformQueryPersonRequest() != null + && isTVNZClientConfigured(profile.getTNVZClient(), profile.isPerformQueryPersonRequest()) + && isMSGClientConfigured(profile.getMSGClient()); + } + + private boolean isTVNZClientConfigured(ClientType tnvzClient, Boolean isPerformQueryPersonRequest) { + return !isPerformQueryPersonRequest || (tnvzClient != null + && tnvzClient.getURL() != null + && tnvzClient.getReceiveTimeout() != null + && tnvzClient.getConnectionTimeout() != null + && isSSLConfigured(tnvzClient)); + } + + private boolean isMSGClientConfigured(ClientType msgClientParams) { + return msgClientParams != null + && msgClientParams.getURL() != null + && isSSLConfigured(msgClientParams) + && msgClientParams.getReceiveTimeout() != null + && msgClientParams.getConnectionTimeout() != null; + } + + private boolean isSSLConfigured(ClientType clientParams) { + return !clientParams.getURL().startsWith("https") || (clientParams.getSSL() != null + && clientParams.getSSL().isTrustAll() != null + && clientParams.getSSL().isLaxHostNameVerification() != null + && isKeyStoreConfigured(clientParams.getSSL().getKeyStore()) + && isTrustStoreConfigured(clientParams.getSSL().getTrustStore())); + } + + private boolean isKeyStoreConfigured(KeyStoreType keyStore) { + return keyStore == null || (keyStore.getPassword() != null + && keyStore.getFileType() != null + && keyStore.getFileName() != null); + } + + private boolean isTrustStoreConfigured(KeyStoreType trustStore) { + return trustStore == null || (trustStore.getPassword() != null + && "JKS".equals(trustStore.getFileType()) + && trustStore.getFileName() != null); + } +} diff --git a/src/main/java/at/gv/egiz/moazs/scheme/MzsService.java b/src/main/java/at/gv/egiz/moazs/scheme/MzsService.java new file mode 100644 index 0000000..f245b93 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/scheme/MzsService.java @@ -0,0 +1,104 @@ +package at.gv.egiz.moazs.scheme; + +import at.gv.egiz.moazs.pipeline.DeliveryPipeline; +import at.gv.egiz.moazs.preprocess.DeliveryRequestAugmenter; +import at.gv.egiz.moazs.repository.DeliveryRepository; +import at.gv.zustellung.app2mzs.xsd.App2MzsPortType; +import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; +import at.gv.zustellung.app2mzs.xsd.DeliveryResponseType; +import at.gv.zustellung.msg.xsd.DeliveryRequestStatusType; +import org.apache.cxf.annotations.SchemaValidation; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import javax.jws.WebParam; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static at.gv.egiz.moazs.MoaZSException.moaZSException; +import static at.gv.egiz.moazs.MoaZSException.moaZSExceptionBuilder; +import static at.gv.zustellung.app2mzs.xsd.PartialSuccessType.partialSuccessTypeBuilder; +import static java.util.concurrent.CompletableFuture.supplyAsync; + +//todo : validate Schema in both directions. +@Service +@SchemaValidation(type = SchemaValidation.SchemaValidationType.IN) +public class MzsService implements App2MzsPortType { + + private static final Logger logger = LoggerFactory.getLogger(MzsService.class); + + //TODO move timeout and namespaces to config + private static final int TIMEOUT_FOR_ANWSER = 10; + + private final DeliveryRepository repository; + private final DeliveryPipeline pipeline; + private final MzsClient appClient; + private final DeliveryRequestAugmenter augmenter; + private final Msg2MzsConverter converter; + + @Autowired + public MzsService(DeliveryRepository repository, DeliveryPipeline pipeline, MzsClient appClient, + DeliveryRequestAugmenter augmenter, Msg2MzsConverter converter) { + this.repository = repository; + this.pipeline = pipeline; + this.appClient = appClient; + this.augmenter = augmenter; + this.converter = converter; + } + + @Override + public DeliveryResponseType app2Mzs( + @WebParam(partName = "DeliveryRequest", + name = "DeliveryRequest") + DeliveryRequestType deliveryRequest) { + + var appDeliveryID = deliveryRequest.getMetaData().getAppDeliveryID(); + + var future = supplyAsync(() -> augmenter.augment(deliveryRequest)) + .thenApply(this::process) + .thenApply(status -> converter.convert(status, repository.getSignedDeliveryRequestStatus(appDeliveryID))); + + try { + return future.get(TIMEOUT_FOR_ANWSER, TimeUnit.SECONDS); + } catch (TimeoutException e) { + logger.info("Answer Timed Out", e); + + future.thenAccept(appClient::sendNotification); + return generatePartialSuccessResponse(appDeliveryID); + + } catch (Exception e) { + throw moaZSExceptionBuilder("An error occurred while processing DeliveryRequest " + + "with AppDeliveryID=%s.", appDeliveryID) + .withCause(e) + .build(); + } + + } + + private DeliveryRequestStatusType process(DeliveryRequestType deliveryRequest) { + + var appDeliveryID = deliveryRequest.getMetaData().getAppDeliveryID(); + logger.info("Receive request with appDeliveryID = {}.", appDeliveryID); + + repository.add(deliveryRequest); + pipeline.processRequest(appDeliveryID); + + return repository.getDeliveryRequestStatus(appDeliveryID) + .orElseThrow(() -> moaZSException("Could not get a response for AppDeliveryID=%s.", appDeliveryID)); + + } + + private DeliveryResponseType generatePartialSuccessResponse(String appDeliveryId) { + + var partial = partialSuccessTypeBuilder() + .withAppDeliveryID(appDeliveryId) + .build(); + + return DeliveryResponseType.deliveryResponseTypeBuilder() + .withPartialSuccess(partial) + .build(); + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/scheme/TnvzHelper.java b/src/main/java/at/gv/egiz/moazs/scheme/TnvzHelper.java new file mode 100644 index 0000000..70c20bb --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/scheme/TnvzHelper.java @@ -0,0 +1,232 @@ +package at.gv.egiz.moazs.scheme; + +import at.gv.egiz.moazs.MoaZSException; +import at.gv.egiz.moazs.scheme.Mzs2MsgConverter; +import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; +import at.gv.zustellung.app2mzs.xsd.persondata.AbstractAddressType; +import at.gv.zustellung.msg.xsd.persondata.IdentificationType; +import at.gv.zustellung.msg.xsd.persondata.ObjectFactory; +import at.gv.zustellung.msg.xsd.persondata.PostalAddressType; +import at.gv.zustellung.tnvz.xsd.*; +import at.gv.zustellung.tnvz.xsd.QueryPersonRequest.QueryEntryList; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.Nullable; +import javax.xml.bind.JAXBElement; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static at.gv.zustellung.tnvz.xsd.PersonQueryType.MetaData.metaDataBuilder; +import static at.gv.zustellung.tnvz.xsd.PersonQueryType.personQueryTypeBuilder; +import static at.gv.zustellung.tnvz.xsd.QueryPersonRequest.QueryEntryList.queryEntryListBuilder; +import static at.gv.zustellung.tnvz.xsd.QueryPersonRequest.queryPersonRequestBuilder; +import static at.gv.zustellung.tnvz.xsd.Receiver.receiverBuilder; +import static at.gv.zustellung.tnvz.xsd.Sender.senderBuilder; +import static java.lang.String.join; +import static java.util.stream.Collectors.toSet; + +@Component +public class TnvzHelper { + + private final Mzs2MsgConverter converter; + + private static final ObjectFactory FACTORY = new at.gv.zustellung.msg.xsd.persondata.ObjectFactory(); + private static final String ENTRY_ID = "entry-id"; + private static final String RECEIVER_NOT_ADRESSABLE_ERROR_MSG = "Receiver is not addressable. Reason: %s"; + private static final String MIMETYPE_MISSMATCH_ERROR_MSG = "Request contains attachment of type(s) %s, but " + + "receiver only accepts attachments of type(s) %s."; + private static final String MZS_NO_TNVZ_PERSON_QUERY_RESULTS_ERROR_MSG = "tnvz:QueryResultList was empty."; + + + @Autowired + public TnvzHelper(Mzs2MsgConverter converter) { + this.converter = converter; + } + + /** + * Performs all tasks related to making a request to the tnvz service: + * Derives a tnvz:QueryPersonRequest from the {@code mzsRequest}, sends the QueryPersonRequest to + * {@code tvnzPort}, validates the tnvz's response and extracts the {@code Identification} Element. + * @param mzsRequest Data source for the QueryPersonRequest + * @param tvnzPort Client for communicating with the tnvz service + * @param exceptionBuilder Utility to collect information and build a meaningful exception in case of errors. + * @throws MoaZSException in case of an error. + * @return + */ + public IdentificationType performQueryPersonRequest(DeliveryRequestType mzsRequest, + TNVZServicePort tvnzPort, + MoaZSException.Builder exceptionBuilder) { + + var tvnzQuery = buildQuery(mzsRequest); + var tvnzResponse = tvnzPort.queryPerson(tvnzQuery); + verifyResponse(tvnzResponse, exceptionBuilder); + + var tvnzResult = getResult(tvnzResponse); + var typesInRequest = extractListOfMimemtypesIn(mzsRequest); + checkMimetypes(tvnzResult, typesInRequest, exceptionBuilder); + + return tvnzResult.getSuccess().getIdentification(); + } + + private QueryPersonRequest buildQuery(DeliveryRequestType mzsRequest) { + + Sender sender = extractSender(mzsRequest.getSender()); + Receiver receiver = extractReceiver(mzsRequest.getReceiver()); + var metadata = extractMetaData(mzsRequest); + + PersonQueryType personQuery = personQueryTypeBuilder() + .withEntryID(ENTRY_ID) + .withSender(sender) + .withReceiver(receiver) + .withMetaData(metadata) + .build(); + + QueryEntryList entryList = queryEntryListBuilder() + .withQueryEntry(List.of(personQuery)) + .build(); + + return queryPersonRequestBuilder() + .withQueryEntryList(entryList) + .build(); + } + + private PersonQueryType.MetaData extractMetaData(DeliveryRequestType request) { + + var builder = metaDataBuilder(); + + var meta = request.getTnvzMetaData(); + + if (meta.getDeliveryQuality() != null) { + builder.withDeliveryQuality(meta.getDeliveryQuality()); + } else { + builder.withPrivateMessageQuality(meta.getPrivateMessageQuality()); + } + + return builder + .withOrigin(meta.getOrigin()) + .withPreAdviceNote(request.getReceiver().getPreAdviceNote()) + .withIgnorePostRedirectionOrder(meta.getIgnorePostRedirectionOrder()) + .build(); + } + + private Sender extractSender(DeliveryRequestType.Sender sender) { + + var corporateBody = sender.getCorporateBody(); + var mzsIdentification = corporateBody.getIdentification().get(0); + var msgIdentification = converter.convert(mzsIdentification); + + var msgPerson = FACTORY.createPerson(converter.convert(corporateBody)); + + return senderBuilder() + .withIdentification(msgIdentification) + .withPerson(msgPerson) + .build(); + } + + private Receiver extractReceiver(DeliveryRequestType.Receiver receiver) { + + var builder = receiverBuilder(); + + if (receiver.getIdentification() == null) { + builder + .withPerson(converter.convert(receiver.getPerson())) + .withAustrianAddressesOnly(receiver.getAustrianAddressesOnly()); + + var postalAddress = findPostalAddress(receiver.getAddress()); + if (postalAddress != null) + builder.withPostalAddress(postalAddress); + + if (receiver.getPreAdviceNote() != null) { + builder.withNotificationAddressList(receiver.getPreAdviceNote().getNotificationAddressList()); + } + } else { + builder.withIdentification(converter.convert(receiver.getIdentification())); + } + + return builder.build(); + } + + private @Nullable PostalAddressType findPostalAddress(List> addresses) { + + if (addresses == null) return null; + + for (JAXBElement address : addresses) { + if(address.getValue() instanceof at.gv.zustellung.app2mzs.xsd.persondata.PostalAddressType) { + var mzsPostalAddress = (at.gv.zustellung.app2mzs.xsd.persondata.PostalAddressType) address.getValue(); + return converter.convert(mzsPostalAddress); + } + } + + return null; + + } + + private Set extractListOfMimemtypesIn(DeliveryRequestType mzsRequest) { + return mzsRequest.getPayload().stream() + .map(DeliveryRequestType.Payload::getMIMEType) + .collect(toSet()); + } + + private void verifyResponse(QueryPersonResponse tvnzResponse, MoaZSException.Builder mzsBuilder) { + + var error = tvnzResponse.getError(); + if (error != null) { + throw mzsBuilder.withErrorCode(error.getCode()) + .withMessage(error.getText()) + .build(); + } + + var results = tvnzResponse.getQueryResultList().getQueryResult(); + if (results.isEmpty()) { + throw mzsBuilder.withErrorCode(MoaZSException.ERROR_MZS_NO_TNVZ_PERSON_QUERY_RESULTS) + .withMessage(MZS_NO_TNVZ_PERSON_QUERY_RESULTS_ERROR_MSG) + .build(); + } + + var tnvzResult = results.get(0); + mzsBuilder.withTnvzResult(tnvzResult); + if (tnvzResult.getError() != null) { + var info = tnvzResult.getError().getErrorInfo(); + throw mzsBuilder.withErrorCode(info.getCode()) + .withMessage(RECEIVER_NOT_ADRESSABLE_ERROR_MSG, info.getText()) + .build(); + } + } + + private PersonResultType getResult(QueryPersonResponse tvnzResponse) { + return tvnzResponse.getQueryResultList().getQueryResult().get(0); + } + + private void checkMimetypes(PersonResultType tnvzResult, Set typesInRequest, MoaZSException.Builder mzsBuilder) { + var mismatchedTypes = findMimeTypeMismatches(tnvzResult, typesInRequest); + if (!mismatchedTypes.isEmpty()) { + var acceptedTypesString = join(",", getAcceptedTypes(tnvzResult)); + var mismatchedTypesString = join(",", mismatchedTypes); + throw mzsBuilder.withErrorCode(MoaZSException.ERROR_MZS_MIMETYPE_MISSMATCH) + .withMessage(MIMETYPE_MISSMATCH_ERROR_MSG, mismatchedTypesString, acceptedTypesString) + .build(); + } + } + + private Collection findMimeTypeMismatches(PersonResultType result, Set typesInRequest) { + var acceptedTypes = getAcceptedTypes(result); + + if (acceptedTypes.contains("*/*")) { + return List.of(); + } + + var typesInRequestCopy = new HashSet<>(typesInRequest); + typesInRequestCopy.removeAll(acceptedTypes); + + return typesInRequestCopy; + } + + private List getAcceptedTypes(PersonResultType result) { + return result.getSuccess().getMimeTypeList().getMimeType(); + } + + +} diff --git a/src/main/java/at/gv/egiz/moazs/tnvz/TnvzHelper.java b/src/main/java/at/gv/egiz/moazs/tnvz/TnvzHelper.java deleted file mode 100644 index f71b992..0000000 --- a/src/main/java/at/gv/egiz/moazs/tnvz/TnvzHelper.java +++ /dev/null @@ -1,232 +0,0 @@ -package at.gv.egiz.moazs.tnvz; - -import at.gv.egiz.moazs.MoaZSException; -import at.gv.egiz.moazs.scheme.Mzs2MsgConverter; -import at.gv.zustellung.app2mzs.xsd.DeliveryRequestType; -import at.gv.zustellung.app2mzs.xsd.persondata.AbstractAddressType; -import at.gv.zustellung.msg.xsd.persondata.IdentificationType; -import at.gv.zustellung.msg.xsd.persondata.ObjectFactory; -import at.gv.zustellung.msg.xsd.persondata.PostalAddressType; -import at.gv.zustellung.tnvz.xsd.*; -import at.gv.zustellung.tnvz.xsd.QueryPersonRequest.QueryEntryList; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.annotation.Nullable; -import javax.xml.bind.JAXBElement; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static at.gv.zustellung.tnvz.xsd.PersonQueryType.MetaData.metaDataBuilder; -import static at.gv.zustellung.tnvz.xsd.PersonQueryType.personQueryTypeBuilder; -import static at.gv.zustellung.tnvz.xsd.QueryPersonRequest.QueryEntryList.queryEntryListBuilder; -import static at.gv.zustellung.tnvz.xsd.QueryPersonRequest.queryPersonRequestBuilder; -import static at.gv.zustellung.tnvz.xsd.Receiver.receiverBuilder; -import static at.gv.zustellung.tnvz.xsd.Sender.senderBuilder; -import static java.lang.String.join; -import static java.util.stream.Collectors.toSet; - -@Component -public class TnvzHelper { - - private final Mzs2MsgConverter converter; - - private static final ObjectFactory FACTORY = new at.gv.zustellung.msg.xsd.persondata.ObjectFactory(); - private static final String ENTRY_ID = "entry-id"; - private static final String RECEIVER_NOT_ADRESSABLE_ERROR_MSG = "Receiver is not addressable. Reason: %s"; - private static final String MIMETYPE_MISSMATCH_ERROR_MSG = "Request contains attachment of type(s) %s, but " + - "receiver only accepts attachments of type(s) %s."; - private static final String MZS_NO_TNVZ_PERSON_QUERY_RESULTS_ERROR_MSG = "tnvz:QueryResultList was empty."; - - - @Autowired - public TnvzHelper(Mzs2MsgConverter converter) { - this.converter = converter; - } - - /** - * Performs all tasks related to making a request to the tnvz service: - * Derives a tnvz:QueryPersonRequest from the {@code mzsRequest}, sends the QueryPersonRequest to - * {@code tvnzPort}, validates the tnvz's response and extracts the {@code Identification} Element. - * @param mzsRequest Data source for the QueryPersonRequest - * @param tvnzPort Client for communicating with the tnvz service - * @param exceptionBuilder Utility to collect information and build a meaningful exception in case of errors. - * @throws MoaZSException in case of an error. - * @return - */ - public IdentificationType performQueryPersonRequest(DeliveryRequestType mzsRequest, - TNVZServicePort tvnzPort, - MoaZSException.Builder exceptionBuilder) { - - var tvnzQuery = buildQuery(mzsRequest); - var tvnzResponse = tvnzPort.queryPerson(tvnzQuery); - verifyResponse(tvnzResponse, exceptionBuilder); - - var tvnzResult = getResult(tvnzResponse); - var typesInRequest = extractListOfMimemtypesIn(mzsRequest); - checkMimetypes(tvnzResult, typesInRequest, exceptionBuilder); - - return tvnzResult.getSuccess().getIdentification(); - } - - private QueryPersonRequest buildQuery(DeliveryRequestType mzsRequest) { - - Sender sender = extractSender(mzsRequest.getSender()); - Receiver receiver = extractReceiver(mzsRequest.getReceiver()); - var metadata = extractMetaData(mzsRequest); - - PersonQueryType personQuery = personQueryTypeBuilder() - .withEntryID(ENTRY_ID) - .withSender(sender) - .withReceiver(receiver) - .withMetaData(metadata) - .build(); - - QueryEntryList entryList = queryEntryListBuilder() - .withQueryEntry(List.of(personQuery)) - .build(); - - return queryPersonRequestBuilder() - .withQueryEntryList(entryList) - .build(); - } - - private PersonQueryType.MetaData extractMetaData(DeliveryRequestType request) { - - var builder = metaDataBuilder(); - - var meta = request.getTnvzMetaData(); - - if (meta.getDeliveryQuality() != null) { - builder.withDeliveryQuality(meta.getDeliveryQuality()); - } else { - builder.withPrivateMessageQuality(meta.getPrivateMessageQuality()); - } - - return builder - .withOrigin(meta.getOrigin()) - .withPreAdviceNote(request.getReceiver().getPreAdviceNote()) - .withIgnorePostRedirectionOrder(meta.getIgnorePostRedirectionOrder()) - .build(); - } - - private Sender extractSender(DeliveryRequestType.Sender sender) { - - var corporateBody = sender.getCorporateBody(); - var mzsIdentification = corporateBody.getIdentification().get(0); - var msgIdentification = converter.convert(mzsIdentification); - - var msgPerson = FACTORY.createPerson(converter.convert(corporateBody)); - - return senderBuilder() - .withIdentification(msgIdentification) - .withPerson(msgPerson) - .build(); - } - - private Receiver extractReceiver(DeliveryRequestType.Receiver receiver) { - - var builder = receiverBuilder(); - - if (receiver.getIdentification() == null) { - builder - .withPerson(converter.convert(receiver.getPerson())) - .withAustrianAddressesOnly(receiver.getAustrianAddressesOnly()); - - var postalAddress = findPostalAddress(receiver.getAddress()); - if (postalAddress != null) - builder.withPostalAddress(postalAddress); - - if (receiver.getPreAdviceNote() != null) { - builder.withNotificationAddressList(receiver.getPreAdviceNote().getNotificationAddressList()); - } - } else { - builder.withIdentification(converter.convert(receiver.getIdentification())); - } - - return builder.build(); - } - - private @Nullable PostalAddressType findPostalAddress(List> addresses) { - - if (addresses == null) return null; - - for (JAXBElement address : addresses) { - if(address.getValue() instanceof at.gv.zustellung.app2mzs.xsd.persondata.PostalAddressType) { - var mzsPostalAddress = (at.gv.zustellung.app2mzs.xsd.persondata.PostalAddressType) address.getValue(); - return converter.convert(mzsPostalAddress); - } - } - - return null; - - } - - private Set extractListOfMimemtypesIn(DeliveryRequestType mzsRequest) { - return mzsRequest.getPayload().stream() - .map(DeliveryRequestType.Payload::getMIMEType) - .collect(toSet()); - } - - private void verifyResponse(QueryPersonResponse tvnzResponse, MoaZSException.Builder mzsBuilder) { - - var error = tvnzResponse.getError(); - if (error != null) { - throw mzsBuilder.withErrorCode(error.getCode()) - .withMessage(error.getText()) - .build(); - } - - var results = tvnzResponse.getQueryResultList().getQueryResult(); - if (results.isEmpty()) { - throw mzsBuilder.withErrorCode(MoaZSException.ERROR_MZS_NO_TNVZ_PERSON_QUERY_RESULTS) - .withMessage(MZS_NO_TNVZ_PERSON_QUERY_RESULTS_ERROR_MSG) - .build(); - } - - var tnvzResult = results.get(0); - mzsBuilder.withTnvzResult(tnvzResult); - if (tnvzResult.getError() != null) { - var info = tnvzResult.getError().getErrorInfo(); - throw mzsBuilder.withErrorCode(info.getCode()) - .withMessage(RECEIVER_NOT_ADRESSABLE_ERROR_MSG, info.getText()) - .build(); - } - } - - private PersonResultType getResult(QueryPersonResponse tvnzResponse) { - return tvnzResponse.getQueryResultList().getQueryResult().get(0); - } - - private void checkMimetypes(PersonResultType tnvzResult, Set typesInRequest, MoaZSException.Builder mzsBuilder) { - var mismatchedTypes = findMimeTypeMismatches(tnvzResult, typesInRequest); - if (!mismatchedTypes.isEmpty()) { - var acceptedTypesString = join(",", getAcceptedTypes(tnvzResult)); - var mismatchedTypesString = join(",", mismatchedTypes); - throw mzsBuilder.withErrorCode(MoaZSException.ERROR_MZS_MIMETYPE_MISSMATCH) - .withMessage(MIMETYPE_MISSMATCH_ERROR_MSG, mismatchedTypesString, acceptedTypesString) - .build(); - } - } - - private Collection findMimeTypeMismatches(PersonResultType result, Set typesInRequest) { - var acceptedTypes = getAcceptedTypes(result); - - if (acceptedTypes.contains("*/*")) { - return List.of(); - } - - var typesInRequestCopy = new HashSet<>(typesInRequest); - typesInRequestCopy.removeAll(acceptedTypes); - - return typesInRequestCopy; - } - - private List getAcceptedTypes(PersonResultType result) { - return result.getSuccess().getMimeTypeList().getMimeType(); - } - - -} diff --git a/src/main/java/at/gv/egiz/moazs/util/ClientFactory.java b/src/main/java/at/gv/egiz/moazs/util/ClientFactory.java new file mode 100644 index 0000000..e7761fe --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/util/ClientFactory.java @@ -0,0 +1,111 @@ +package at.gv.egiz.moazs.util; + +import at.gv.zustellung.app2mzs.xsd.ClientType; +import at.gv.zustellung.app2mzs.xsd.KeyStoreType; +import at.gv.zustellung.app2mzs.xsd.SSLType; +import org.apache.cxf.configuration.jsse.TLSClientParameters; +import org.apache.cxf.endpoint.Client; +import org.apache.cxf.frontend.ClientProxy; +import org.apache.cxf.jaxws.JaxWsClientFactoryBean; +import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; +import org.apache.cxf.transport.http.HTTPConduit; +import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.xml.ws.BindingProvider; +import javax.xml.ws.soap.SOAPBinding; + +import static at.gv.zustellung.app2mzs.xsd.KeyStoreType.keyStoreTypeBuilder; + +@Component +public class ClientFactory { + + private static final Logger log = LoggerFactory.getLogger(ClientFactory.class); + + private final StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor; + private final SSLContextCreator sslContextCreator; + private final FileUtils fileUtils; + + @Autowired + public ClientFactory(StoreSOAPBodyBinaryInRepositoryInterceptor storeResponseInterceptor, + SSLContextCreator creator, + FileUtils fileUtils) { + this.storeResponseInterceptor = storeResponseInterceptor; + this.sslContextCreator = creator; + this.fileUtils = fileUtils; + } + + /** + * Creates a client that communicates with a soap service. + * + * @param params for the client, such as service url and ssl parameters. + * @return the client + */ + public T create(ClientType params, Class clazz) { + + var factory = new JaxWsClientFactoryBean(); + factory.setServiceClass(clazz); + factory.setAddress(params.getURL()); + factory.getInInterceptors().add(storeResponseInterceptor); + + var proxy = new JaxWsProxyFactoryBean(factory).create(); + Client client = ClientProxy.getClient(proxy); + HTTPConduit http = (HTTPConduit) client.getConduit(); + + var bindingProvider = (BindingProvider) proxy; + SOAPBinding binding = (SOAPBinding) bindingProvider.getBinding(); + binding.setMTOMEnabled(true); + + var httpClientPolicy = new HTTPClientPolicy(); + httpClientPolicy.setConnectionTimeout(params.getConnectionTimeout().longValueExact()); + httpClientPolicy.setReceiveTimeout(params.getReceiveTimeout().longValueExact()); + http.setClient(httpClientPolicy); + + if (params.getURL().startsWith("https")) { + TLSClientParameters tlsParams = setupTLSParams(params.getSSL()); + http.setTlsClientParameters(tlsParams); + log.info("SSLContext initialized. "); + } + + return ((T)proxy); + } + + private TLSClientParameters setupTLSParams(SSLType ssl) { + + var tlsParams = new TLSClientParameters(); + var keystore = resolveKeyStorePath(ssl.getKeyStore()); + + SSLContext sslContext; + if (ssl.isTrustAll()) { + sslContext = sslContextCreator.createUnsafeSSLContext(keystore); + } else { + var truststore = resolveKeyStorePath(ssl.getTrustStore()); + sslContext = sslContextCreator.createSSLContext(keystore, truststore); + } + tlsParams.setSSLSocketFactory(sslContext.getSocketFactory()); + + if (ssl.isLaxHostNameVerification()) { + tlsParams.setDisableCNCheck(true); + } + + return tlsParams; + } + + private KeyStoreType resolveKeyStorePath(@Nullable KeyStoreType store) { + + if (store == null) return null; + + var resolvedURI = "file:" + fileUtils.determinePath(store.getFileName()); + log.trace("Resolved key store path from {} to {}.", store.getFileName(), resolvedURI); + + return keyStoreTypeBuilder(store) + .withFileName(resolvedURI) + .build(); + } + +} diff --git a/src/main/java/at/gv/egiz/moazs/util/StoreSOAPBodyBinaryInRepositoryInterceptor.java b/src/main/java/at/gv/egiz/moazs/util/StoreSOAPBodyBinaryInRepositoryInterceptor.java new file mode 100644 index 0000000..2db81ab --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/util/StoreSOAPBodyBinaryInRepositoryInterceptor.java @@ -0,0 +1,64 @@ +package at.gv.egiz.moazs.util; + +import at.gv.egiz.moazs.repository.DeliveryRepository; +import at.gv.egiz.moazs.scheme.SOAPUtils; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.AbstractPhaseInterceptor; +import org.apache.cxf.phase.Phase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import static at.gv.egiz.moazs.MoaZSException.moaZSException; + +@Component +public class StoreSOAPBodyBinaryInRepositoryInterceptor extends AbstractPhaseInterceptor { + + private static final Logger log = LoggerFactory.getLogger(StoreSOAPBodyBinaryInRepositoryInterceptor.class); + + private final CXFMessageUtils messageUtils; + private final SOAPUtils soapUtils; + private final DeliveryRepository repository; + + @Autowired + public StoreSOAPBodyBinaryInRepositoryInterceptor(CXFMessageUtils extractor, SOAPUtils soapUtils, + DeliveryRepository repository) { + super(Phase.RECEIVE); + this.messageUtils = extractor; + this.soapUtils = soapUtils; + this.repository = repository; + } + + public void handleMessage(Message message) { + + try { + byte[] content = messageUtils.copyContent(message); + + if(log.isTraceEnabled()) { + log.trace("Interceptor received this SOAP message: {}. ", new String(content, StandardCharsets.UTF_8)); + } + + if(content.length > 0) { + Element document = soapUtils.toDOM(content); + byte[] status = soapUtils.unwrapSoapEnvelope(document); + String appDeliveryID = soapUtils.getAppDeliveryIDFrom(document); + repository.addSignedDeliveryRequestStatus(status, appDeliveryID); + + if(log.isTraceEnabled()) { + log.trace("DeliveryRequestStatus with AppDeliveryID={} unwrapped and stored: {}. ", + appDeliveryID, new String(status, StandardCharsets.UTF_8)); + } + } + } catch (ParserConfigurationException | SAXException | IOException | NullPointerException e) { + throw moaZSException("Could not extract signed data from message.", e); + } + } + +} -- cgit v1.2.3