diff options
8 files changed, 224 insertions, 102 deletions
diff --git a/src/main/java/at/gv/egiz/moazs/MoaZSException.java b/src/main/java/at/gv/egiz/moazs/MoaZSException.java index 499dc14..1ffc1ef 100644 --- a/src/main/java/at/gv/egiz/moazs/MoaZSException.java +++ b/src/main/java/at/gv/egiz/moazs/MoaZSException.java @@ -8,7 +8,6 @@ public class MoaZSException extends RuntimeException { public static final String ERROR_MZS_MIMETYPE_MISSMATCH = "8001"; public static final String ERROR_MZS_NO_TNVZ_PERSON_QUERY_RESULTS = "8002"; public static final String ERROR_MZS_BINARY_RESPONSE_MISSING = "8003"; - public static final String ERROR_MZS_RESPONSE_MISSING = "8004"; public static final String ERROR_MOASP_SIGNATURE_INVALID = "7001"; @Nullable private final String code; 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 8649a32..df0f83e 100644 --- a/src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java +++ b/src/main/java/at/gv/egiz/moazs/backend/MsgResponseBackend.java @@ -4,7 +4,6 @@ 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; @@ -25,25 +24,19 @@ public class MsgResponseBackend implements Consumer<String> { "with AppDeliveryID=%s is not valid."; public static final String BINARY_RESPONSE_MISSING_ERROR_MSG = "Binary Response is not in repository."; public static final String RESPONSE_MISSING_ERROR_MSG = "Response with ResponseID=%s is not in repository."; + public static final String REQUEST_MISSING_ERROR_MSG = "Request with AppDeliveryID=%s is not in repository."; private final DeliveryRepository repository; private final Consumer<byte[]> signatureVerifier; - private final SaveResponseToFileSink saveResponseSink; - private final LogResponseSink logResponseSink; - private final ForwardResponseToServiceSink forwardResponseSink; + private final MsgResponseSinkHub hub; @Autowired public MsgResponseBackend(DeliveryRepository repository, Consumer<byte[]> signatureVerifier, - SaveResponseToFileSink saveResponseToFileSink, - LogResponseSink logResponseSink, - ForwardResponseToServiceSink forwardResponseSink) { + MsgResponseSinkHub hub) { this.repository = repository; this.signatureVerifier = signatureVerifier; - this.saveResponseSink = saveResponseToFileSink; - this.logResponseSink = logResponseSink; - this.forwardResponseSink = forwardResponseSink; - + this.hub = hub; } /** @@ -58,8 +51,7 @@ public class MsgResponseBackend implements Consumer<String> { */ @Override public void accept(String responseID) { - - supplyAsync(() -> verify(responseID)) + supplyAsync(() -> verifySignature(responseID)) .thenAcceptAsync(msgResponse -> applySinks(msgResponse)) .exceptionally(ex -> { log.error(ex.getMessage(), ex); @@ -67,7 +59,7 @@ public class MsgResponseBackend implements Consumer<String> { }); } - private MsgResponse verify(String responseID) { + private MsgResponse verifySignature(String responseID) { var response = repository.retrieveResponse(responseID).orElseThrow( ()-> moaZSException(format(RESPONSE_MISSING_ERROR_MSG, responseID))); @@ -87,28 +79,12 @@ public class MsgResponseBackend implements Consumer<String> { } private void applySinks(MsgResponse msgResponse) { - - var sinkParams = getSinkParams(msgResponse); - - if (sinkParams.getSaveResponseToFile().isActive()) { - supplyAsync(() -> saveResponseSink.save(msgResponse, sinkParams.getSaveResponseToFile().getPath())); - } - - if (sinkParams.isLogResponse()) { - supplyAsync(() -> logResponseSink.log(msgResponse)); - } - - if (sinkParams.getForwardResponseToService().isActive()) { - supplyAsync(() -> forwardResponseSink.send( - msgResponse, sinkParams.getForwardResponseToService().getMzsClient())); - } - - } - - private MsgResponseSinksType getSinkParams(MsgResponse msgResponse) { var appDeliveryID = msgResponse.getAppDeliveryID(); - var request = repository.retrieveDeliveryRequest(appDeliveryID).get(); - return request.getConfig().getMsgResponseSinks(); + var request = repository.retrieveDeliveryRequest(appDeliveryID).orElseThrow( + ()-> moaZSException(format(REQUEST_MISSING_ERROR_MSG, appDeliveryID))); + + var sinkParams = request.getConfig().getMsgResponseSinks(); + hub.applySinks(msgResponse, sinkParams); } } diff --git a/src/main/java/at/gv/egiz/moazs/backend/MsgResponseSinkHub.java b/src/main/java/at/gv/egiz/moazs/backend/MsgResponseSinkHub.java new file mode 100644 index 0000000..0df5c68 --- /dev/null +++ b/src/main/java/at/gv/egiz/moazs/backend/MsgResponseSinkHub.java @@ -0,0 +1,44 @@ +package at.gv.egiz.moazs.backend; + +import at.gv.egiz.moazs.scheme.MsgResponse; +import at.gv.zustellung.app2mzs.xsd.MsgResponseSinksType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +@Component +public class MsgResponseSinkHub { + + private final SaveResponseToFileSink saveResponseSink; + private final LogResponseSink logResponseSink; + private final ForwardResponseToServiceSink forwardResponseSink; + + @Autowired + public MsgResponseSinkHub(SaveResponseToFileSink saveResponseSink, LogResponseSink logResponseSink, + ForwardResponseToServiceSink forwardResponseSink) { + this.saveResponseSink = saveResponseSink; + this.logResponseSink = logResponseSink; + this.forwardResponseSink = forwardResponseSink; + } + + /** + * Apply all sinks that are configured in {@code sinkParams} to {@code msgResponse}. + */ + public void applySinks(MsgResponse msgResponse, MsgResponseSinksType sinkParams) { + + if (sinkParams.getSaveResponseToFile().isActive()) { + supplyAsync(() -> saveResponseSink.save(msgResponse, sinkParams.getSaveResponseToFile().getPath())); + } + + if (sinkParams.isLogResponse()) { + supplyAsync(() -> logResponseSink.log(msgResponse)); + } + + if (sinkParams.getForwardResponseToService().isActive()) { + supplyAsync(() -> forwardResponseSink.send( + msgResponse, sinkParams.getForwardResponseToService().getMzsClient())); + } + + } +} diff --git a/src/main/java/at/gv/egiz/moazs/backend/SignatureVerifier.java b/src/main/java/at/gv/egiz/moazs/backend/SignatureVerifier.java index 874e4f4..a19c06a 100644 --- a/src/main/java/at/gv/egiz/moazs/backend/SignatureVerifier.java +++ b/src/main/java/at/gv/egiz/moazs/backend/SignatureVerifier.java @@ -3,7 +3,6 @@ package at.gv.egiz.moazs.backend; import at.gv.egiz.eid.authhandler.modules.sigverify.moasig.api.ISignatureVerificationService; import at.gv.egiz.eid.authhandler.modules.sigverify.moasig.api.data.IXMLSignatureVerificationResponse; import at.gv.egiz.eid.authhandler.modules.sigverify.moasig.exceptions.MOASigServiceException; -import at.gv.egiz.moazs.MoaZSException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -21,19 +20,17 @@ public class SignatureVerifier implements Consumer<byte[]> { private static final String CERT_CODE_ERROR_MSG = "Certificate chain is not valid: Check code was %d. "; private static final String MANIFEST_CODE_ERROR_MSG = "Signature Manifest is not valid: Check code was %d. "; private static final String XMLMANIFEST_CODE_ERROR_MSG = "XmlDSIGManifest is not valid: Check code was %d. "; - - //TODO: Dont make this multiline! private static final String XML_SIGNATURE_RESPONSE_TEMPLATE = - " XmlDsigSubjectName: %s\n" + - " SignatureManifestCheckCode: %s\n" + - " XmlDSIGManifestCheckCode: %s\n" + - " CertificateCheckCode: %s\n" + - " SignatureCheckCode: %s\n" + - " SigningDateTime: %s\n" + - " isXmlDSIGManigest: %s\n" + - " isPublicAuthority: %s\n" + - " isQualifiedCertificate: %s\n" + - " getPublicAuthorityCode: %s\n"; + "XmlDsigSubjectName: %s; " + + "SignatureManifestCheckCode: %s; " + + "XmlDSIGManifestCheckCode: %s; " + + "CertificateCheckCode: %s; " + + "SignatureCheckCode: %s; " + + "SigningDateTime: %s; " + + "isXmlDSIGManigest: %s; " + + "isPublicAuthority: %s; " + + "isQualifiedCertificate: %s; " + + "getPublicAuthorityCode: %s; "; private static final String MOASIG_SERVICE_ERROR_MSG = "MOA SPSS could not accept the XML signature. "; private final ISignatureVerificationService service; @@ -48,8 +45,7 @@ public class SignatureVerifier implements Consumer<byte[]> { } /** - * Verifies the signature of a signed XML document. Throws a at.gv.egiz.moazs.MoaZSException exception - * if the validation fails. + * Verifies the signature of a signed XML document. If the validation fails, it will throw an exception. * @param signedXMLdocument * @throws at.gv.egiz.moazs.MoaZSException */ @@ -107,7 +103,7 @@ public class SignatureVerifier implements Consumer<byte[]> { public static void debug(IXMLSignatureVerificationResponse response) { if (log.isDebugEnabled()) { - var builder = new StringBuilder("Response: \n"); + var builder = new StringBuilder("Response: "); if (response == null) { builder.append("null"); } else { 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 a3329cd..056f6dc 100644 --- a/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java +++ b/src/main/java/at/gv/egiz/moazs/preprocess/ConfigUtil.java @@ -31,7 +31,7 @@ public class ConfigUtil { public static final String FILENAME_KEY = "filename"; public static final String FILETYPE_KEY = "filetype"; public static final String PASSWORD_KEY = "password"; - public static final String RECEIVE_TIMEOUT = "receive-timeout"; + public static final String RECEIVE_TIMEOUT_KEY = "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"; @@ -40,6 +40,7 @@ public class ConfigUtil { public static final String SAVE_RESPONSE_TO_FILE_PATH_KEY = "path"; public static final String FORWARD_RESPONSE_TO_SERVICE_KEY = "forward-response-to-service"; public static final String MZS_CLIENT_KEY = "mzs-client"; + public static final String SERVICE_TIMEOUT_KEY = "service-timeout"; /** @@ -64,11 +65,14 @@ public class ConfigUtil { MsgResponseSinksType sinks = msgResponseSinksParams.isEmpty() ? null : buildMsgResponseSinks(msgResponseSinksParams); + var serviceTimeout = bigIntOrNull(values.get(SERVICE_TIMEOUT_KEY)); + return ConfigType.configTypeBuilder() .withPerformQueryPersonRequest(performQueryPersonRequest) .withMSGClient(msgClient) .withTNVZClient(tnvzClient) .withMsgResponseSinks(sinks) + .withServiceTimeout(serviceTimeout) .build(); } @@ -83,13 +87,9 @@ public class ConfigUtil { var url = clientParams.get(URL_KEY); - BigInteger connectionTimeout = clientParams.containsKey(CONNECTION_TIMEOUT_KEY) - ? new BigInteger(clientParams.get(CONNECTION_TIMEOUT_KEY)) - : null; + var connectionTimeout = bigIntOrNull(clientParams.get(CONNECTION_TIMEOUT_KEY)); - BigInteger receiveTimeout = clientParams.containsKey(RECEIVE_TIMEOUT) - ? new BigInteger(clientParams.get(RECEIVE_TIMEOUT)) - : null; + var receiveTimeout = bigIntOrNull(clientParams.get(RECEIVE_TIMEOUT_KEY)); var sslParams = filterMapByPrefix(clientParams, SSL_KEY); SSLType ssl = sslParams.isEmpty() @@ -179,6 +179,11 @@ public class ConfigUtil { return value == null ? null : Boolean.getBoolean(value); } + private BigInteger bigIntOrNull(String value) { + return value == null + ? null + : new BigInteger(value); + } /** * Combine properties of two Configs; {@code primary} overrides {@code fallback}. @@ -207,6 +212,10 @@ public class ConfigUtil { builder.withMsgResponseSinks(merge(primary.getMsgResponseSinks(), fallback.getMsgResponseSinks())); } + if (primary.getServiceTimeout() != null) { + builder.withServiceTimeout(primary.getServiceTimeout()); + } + return builder.build(); } diff --git a/src/main/java/at/gv/egiz/moazs/service/MzsService.java b/src/main/java/at/gv/egiz/moazs/service/MzsService.java index 8f0ef86..c6871da 100644 --- a/src/main/java/at/gv/egiz/moazs/service/MzsService.java +++ b/src/main/java/at/gv/egiz/moazs/service/MzsService.java @@ -1,5 +1,6 @@ package at.gv.egiz.moazs.service; +import at.gv.egiz.moazs.backend.MsgResponseSinkHub; import at.gv.egiz.moazs.preprocess.DeliveryRequestAugmenter; import at.gv.egiz.moazs.repository.DeliveryRepository; import at.gv.egiz.moazs.scheme.Msg2MzsConverter; @@ -7,7 +8,6 @@ import at.gv.egiz.moazs.scheme.RequestStatusResponse; 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; @@ -31,46 +31,57 @@ 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 static final String MZS_SERVICE_ERROR_MSG = "An error occurred while processing DeliveryRequest " + "with AppDeliveryID=%s."; private static final String RESPONSE_MISSING_ERROR_MSG = "Could not get a response for AppDeliveryID=%s."; + private static final String SERVICE_TIME_OUT_REACHED_MSG = "Backend processing for DeliveryRequest with " + + "AppDeliveryID=%s timed out. "; private final DeliveryRepository repository; private final Consumer<String> backend; private final DeliveryRequestAugmenter augmenter; private final Msg2MzsConverter converter; + private final MsgResponseSinkHub hub; @Autowired public MzsService(DeliveryRepository repository, Consumer<String> deliveryRequestBackend, - DeliveryRequestAugmenter augmenter, Msg2MzsConverter converter) { + DeliveryRequestAugmenter augmenter, Msg2MzsConverter converter, + MsgResponseSinkHub hub) { this.repository = repository; this.backend = deliveryRequestBackend; this.augmenter = augmenter; this.converter = converter; + this.hub = hub; } @Override public DeliveryResponseType app2Mzs( - @WebParam(partName = "DeliveryRequest", - name = "DeliveryRequest") - DeliveryRequestType deliveryRequest) { + @WebParam(partName = "DeliveryRequest",name = "DeliveryRequest") DeliveryRequestType deliveryRequest) { var appDeliveryID = deliveryRequest.getMetaData().getAppDeliveryID(); - var responseID = RequestStatusResponse.getResponseID(appDeliveryID); - var future = supplyAsync(() -> augmenter.augment(deliveryRequest)) - .thenApply(this::process) - .thenApply(status -> converter.convert(status, repository.retrieveBinaryResponse(responseID))); + var completeRequest = augmenter.augment(deliveryRequest); + + var requestProcessed = supplyAsync(() -> process(completeRequest)); try { - return future.get(TIMEOUT_FOR_ANWSER, TimeUnit.SECONDS); - } catch (TimeoutException e) { - logger.info("Answer Timed Out", e); + var serviceTimeout = completeRequest.getConfig().getServiceTimeout(); + RequestStatusResponse response; + + if (serviceTimeout == null) { + response = requestProcessed.get(); + } else { + response = requestProcessed.get(serviceTimeout.longValue(), TimeUnit.SECONDS); + } + + var status = response.getResponse(); + var binaryStatus = repository.retrieveBinaryResponse(response.getResponseID()); + return converter.convert(status, binaryStatus); - //TODO: revise how notification should be sent - //future.thenAccept(appClient::sendNotification); + } catch (TimeoutException e) { + logger.info(format(SERVICE_TIME_OUT_REACHED_MSG, appDeliveryID), e); + var sinkParams = completeRequest.getConfig().getMsgResponseSinks(); + requestProcessed.thenAcceptAsync(response -> hub.applySinks(response, sinkParams)); return generatePartialSuccessResponse(appDeliveryID); } catch (Exception e) { @@ -80,19 +91,18 @@ public class MzsService implements App2MzsPortType { } - private DeliveryRequestStatusType process(DeliveryRequestType deliveryRequest) { + private RequestStatusResponse process(DeliveryRequestType deliveryRequest) { var appDeliveryID = deliveryRequest.getMetaData().getAppDeliveryID(); - //TODO: fix too. - logger.info("Receive request with appDeliveryID = {}.", appDeliveryID); repository.store(deliveryRequest); backend.accept(appDeliveryID); - var statusId = RequestStatusResponse.getResponseID(appDeliveryID); - var response = repository.retrieveResponse(statusId) + var responseID = RequestStatusResponse.getResponseID(appDeliveryID); + + return (RequestStatusResponse) repository.retrieveResponse(responseID) .orElseThrow(() -> moaZSException(format(RESPONSE_MISSING_ERROR_MSG, appDeliveryID))); - return (DeliveryRequestStatusType) response.getResponse(); + } private DeliveryResponseType generatePartialSuccessResponse(String appDeliveryId) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 53d1951..7e2797f 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,4 +1,4 @@ -### Logging +# Optional logging: level: root: WARN @@ -9,49 +9,118 @@ logging: SignatureVerifier: DEBUG LogResponseSink: INFO -# Default type for java's ssl key/trust store +# Mandatory +# Default type for java's ssl key/trust store. When in doubt, set to +# jks. javax.net.ssl: keyStoreType: jks trustStoreType: jks -# Configure parameters here or in DeliveryRequest/Config. -# Choose a profile in DeliveryRequest/Config/ProfileId. -# If a parameter is missing, moa zs falls back to "default" -# Order: DeliveryRequest/Config > [chosen-profile] > default +# Mandatory +# Defines configuration profiles. Each profile contains all parameters +# that are interpreted by moa-zs to determine how a delivery request +# should be processed. Configure parameters here and/or in +# DeliveryRequest/Config. You can select a profile per delivery +# request by referring to the profile id in +# DeliveryRequest/Config/ProfileId. If a parameter is missing, moa-zs +# falls back to "default" You can override parameters in profiles. +# Parameter have the following (descending) priority: +# DeliveryRequest/Config > [chosen-profile] > default delivery-request-configuration-profiles: + + # Mandatory + # The "default" profile. Will be chosen if + # DeliveryRequest/Config/ProfileId is missing. default: + + # Optional + # Specifies in seconds, how long the mzs service maximally waits + # for a delivery request to complete. If the timeout is reached, + # the service will reply with "PartialSuccess" and handle + # responses asynchronously. A missing service-timeout means that the + # service waits indefinitely. See also: msg-response-sinks, which + # allows you to configure, how moa-zs handles asynchronous + # responses. + service-timeout: 60 + + # Mandatory + # If true, moa-zs asks the tnvz service if the receiver is + # addressable. Requires setting up the tvnz-client. perform-query-person-request: false + + # Optional (Mandatory if perform-query-person-request is true) + # Parameters for the connection to tvnz. Specify url, + # connection-timeout, receive-timeout and ssl here. See msg-client + # for an exhaustive description of all parameters. + tvnz-client: + + # Mandatory + url: http://localhost:8082/tnvz/ + + # Optional + # ssl: ... + # connection-timeout: ... + # receive-timeout: ... + + # Mandatory + # Parameters for the connection to msg. msg-client: + + # Mandatory url: http://localhost:8081/services/DeliveryRequest - # Time in ms after which a connection will be closed. - # 0 means indefinitely. + + # Mandatory + # Time in ms after which a connection will be closed. 0 means + # indefinitely. connection-timeout: 0 - # Time in ms that the client waits after having sent the request. - # 0 means indefinitely. + + # Mandatory + # Time in ms that the client waits after having sent the + # request. 0 means indefinitely. receive-timeout: 0 - # Specifies how MoaZS should a synchronous responses from msg. + # Mandatory + # Specifies how moa-zs should process asynchronous responses from msg. msg-response-sinks: + + # Mandatory + # Save response on the file system under the folder "path". save-response-to-file: + # Mandatory active: false + # Mandatory if activated path: /msg-responses/ + + # Mandatory + # Log response to the at.gv.egiz.moazs.backend.LogResponseSink Logger. log-response: true + + # Mandatory + # Forward the response to mzs service. forward-response-to-service: + # Mandatory active: false + # Mandatory if activated mzs-client: - # TODO: ensure that only one url is needed to - # sent DeliveryRequestStatus / DeliveryNotifications + # TODO: ensure that only one url is needed to sent DeliveryRequestStatus / DeliveryNotifications url: http://service.which.implements.mzs2app.wsdl/services/ # connection-timeout # receive-timeout # ssl... + # Optional + # Add your own profiles (at wish) here. Follow the same structure as the "default" profile. + # Override parameters at wish. + + # Example + # "ssl-profile" is an example for a profile that overrides msg client parameters + # to protect the msg connection with ssl client authentication. ssl-profile: msg-client: url: https://localhost/zusemsg/services/DeliveryRequest ssl: ## Boolean; if true, app will trust all server certificates; - ## if false, server certificate needs to be in truststore. + ## if false, server certificate needs to be in the truststore. trust-all: false ## Boolean; if true, app ignores mismatches between server's host name and ## Certificate's common name / alternative subject name. @@ -65,32 +134,50 @@ delivery-request-configuration-profiles: ## JKS or PKCS12 type: PKCS12 +# Optional # 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 +# Mandatory +# Moa Spss Configuration moa.spss: + + # Mandatory is-active: true - # if active, moa spss will validate manifests in xml signatures + + # Mandatory + # If active, moa spss validates manifests in xml signatures. is-manifest-check-active: false + + # Mandatory server: - # path that points to MoaSPSSConfiguration file; can be: + + # Mandatory + # Path that points to MoaSPSSConfiguration file; can be: # - absolute path (unix: starts with /), or # - relative path (otherwise, relative to application's class path) configuration: moa-spss/MOASPSSConfiguration.xml + + # Mandatory + # Select, which trust-profile moa spss uses to verify a signature. default-trustprofile: test-trustprofile +# Optional +# Redis Setup (Cluster Mode) spring: redis: host: 172.17.0.2 port: 6379 +# Mandatory repository: - # duration in minutes before repository records are evicted. + + # Mandatory + # Duration in minutes before repository records are evicted. expiresAfterWrite: 30 -## activate cluster mode -# profiles: -# active: cluster +# Optional +# activate cluster mode +# profiles.active: cluster diff --git a/src/main/resources/mzs/app2mzs.xsd b/src/main/resources/mzs/app2mzs.xsd index da49631..7e70092 100644 --- a/src/main/resources/mzs/app2mzs.xsd +++ b/src/main/resources/mzs/app2mzs.xsd @@ -95,6 +95,7 @@ <xs:complexType name="ConfigType"> <xs:sequence> <xs:element name="ProfileID" type="xs:token" minOccurs="0"></xs:element> + <xs:element name="ServiceTimeout" type="xs:nonNegativeInteger" minOccurs="0"/> <xs:element name="PerformQueryPersonRequest" type="xs:boolean" minOccurs="0" /> <xs:element ref="MSGClient" minOccurs="0" /> <xs:element ref="TNVZClient" minOccurs="0" /> |