diff --git a/README.md b/README.md index 1a39d053be8d8a5093f7e8b28f93e995f5aba772..a08a5ccfcc5c9ce42d415ab99e8e9882d0bec457 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,248 @@ -# XTA Client Bibliothek +# XTA-Client + +Ein Client zum Senden und Empfangen von XTA Nachrichten. + + + +Der Client verwendet *XTA 2 Version 3.1* mit [XTA-Modul-Webservice-Version](https://www.xoev.de/osci-xta/standard-xta-2/xta-versionsuebersicht-23036) `2.1.1`. +Er wird als Maven-Artefakt `xta-client-lib` bereitgestellt und kann in Java-Anwendungen eingebunden werden. + +## Senden einer Nachricht +Zum Senden einer Nachricht konfiguriert die Nutzer*in einen neuen XTA-Client mit einem Client-Zertifikat, welches sie für das Senden mit einer Autor-Kennung autorisiert. + +Die Nutzer*in erstellt die zu sendende Nachricht mit entsprechender Autor- und Leser-Kennung. +Anschließend übergibt sie die Nachricht an den XTA-Server mit einem Aufruf von `XtaClient::sendMessage`. +Wenn der zurückgegebene Transport-Report den Status `OPEN` meldet, steht die Nachricht dem Leser zur Abholung bereit. +```mermaid +%% Senden einer Nachricht + sequenceDiagram + participant A as Nutzer*in + participant B as XTA-Client + participant C as XTA-Server + Note left of A: (1) XTA-Client erzeugen + activate A + Note left of A: (2) Nachricht erzeugen + Note left of A: (3) Nachricht senden + A->>B: sendMessage + activate B + B->>C: checkAccountActive + activate C + C-->>B: + deactivate C + B->>C: lookupService + activate C + C-->>B: + deactivate C + B->>C: createMessageID + activate C + C-->>B: + deactivate C + B->>C: sendMessage + activate C + C-->>B: + deactivate C + B->>C: getTransportReport + activate C + C-->>B: + deactivate C + B-->>A: + deactivate B + Note left of A: (4) Nachrichtenstatus prüfen + deactivate A +``` + +<small>* Der XTA-Server wird oft als Nachrichtenbroker oder Nachrichtenvermittler bezeichnet.</small> + +**Beispielcode:** + +```java +// (1) XTA-Client erzeugen +var client = XtaClient.from(config); +// (2) Nachricht erzeugen +var zipFileName = "d5be7468-e620-4126-a40e-61a7f9b46345_Geschaeftsgang.Geschaeftsgang.0201.zip"; +var zipFileContentDataHandler = new DataHandler(new FileDataSource("/path/to/" + zipFileName)); +var xdomeaXtaFile = XtaFile.builder() + .name(zipFileName) + .content(zipFileContentDataHandler) + .contentType("application/zip") + .build(); + +var message = XtaMessage.builder() + .metaData(XtaMessageMetaData.builder() + .service("urn:xoev-de:xdomea:schema:2.4.0/xdomea240Antrag.wsdl") + .businessScenarioCode("XDOMEAGAD_DATA") + .businessScenarioListUri("urn:de:dataport:codeliste:business.scenario") + .businessScenarioListVersionId("1.0") + .messageTypeCode("Geschaeftsgang.Geschaeftsgang.0201") + .messageTypePayloadSchema("urn:xoev-de:xdomea:schema:2.4.0") + .authorIdentifier(XtaIdentifier.builder() + .category("Generischer Antragsdienst") + .value("gad:010200100000") + .build()) + .readerIdentifier(XtaIdentifier.builder() + .category("Generischer Antragsempfänger") + .value("gae:test-environment@ozg-cloud.de") + .build()) + .build()) + .messageFile(xdomeaXtaFile) + .attachmentFiles(emptyList()) + .build(); +// (3) Nachricht senden +var transportReport = client.sendMessage(message); +// (4) Nachrichtenstatus prüfen +assert transportReport.status() == XtaMessageStatus.OPEN; +``` + +Da eine Xdomea-Nachricht die Leser- und Autor-Kennung bereits enthält, ist es möglich aus der Xdomea-ZIP-Datei die Metadaten der XTA-Nachricht abzuleiten: + +```java +// ... +// (2) Nachricht erzeugen +// ... +var message = XdomeaXtaMessageCreator.createInstance().createMessage(xdomeaXtaFile); +// (3) Nachricht senden +var transportReport = client.sendMessage(message); +// ... +``` + +Hierbei wird die Xta-Autor-Kennung aus dem *Xdomea-Absender-Kennung/Behoerdenschluessel* gelesen und die Xta-Leser-Kennung aus dem *Xdomea-Empfaenger-Kennung/Behoerdenschluessel*. + +### Validierung der Xdomea-ZIP-Datei +Wenn eine `XtaMessage` für eine Xdomea-ZIP-Datei mit `XdomeaXtaMessageCreator::createMessage` erstellt wird, wird zudem geprüft, ob die Nachricht die ["Transportfestlegungen Generischer Antragsdienst"](doku/Transportfestlegungen%20Generischer%20Antragsdienst.pdf) einhält. + +Für eine erfolgreiche Validierung muss eine Xdomea-ZIP-Datei folgende Bedingungen erfüllen: +- Der ZIP-Dateiname ist `<ProzessID>_<Nachrichtentyp>.zip` (z.B. `00000000-0000-0000-0000-_Geschaeftsgang.Geschaeftsgang.0201.zip`) + - Die ProzessID in der Xdomea-Nachricht entspricht der ProzessID im Dateinamen +- Die Xdomea-Nachricht `<ProzessID>_<Nachrichtentyp>.xml` in der ZIP-Datei ist schema-konform. +- Der Xdomea-Nachrichten-Typ ist zulässig: + - `Geschaeftsgang.Geschaeftsgang.0201` mit Xdomea 2.4.0 + - `Abgabe.Abgabe.0401` mit Xdomea 3.0.0 + - `Abgabe.ImportBestaetigen.0402` mit Xdomea 3.0.0 +- Alle Dateien, die die Xdomea-Nachricht referenziert, sind in der ZIP-Datei enthalten. +- Eine Absender-Kennung ist vorhanden mit dem Präfix `gad` +- Eine Empfänger-Kennung ist vorhanden mit dem Präfix `gae` + +<small>Hinweis: Die Xdomea-Nachricht sollte mit UTF-8 kodiert sein.</small> + +## Empfangen von Nachrichten +Zum Empfangen von Nachrichten konfiguriert die Nutzer*in einen neuen XTA-Client mit den gewünschten Leser-Kennungen. +Zudem konfiguriert er das Client-Zertifikat, welches ihn zum Lesen mit den Leser-Kennungen autorisiert. + +Beim Aufruf von `fetchMessages` werden alle Nachrichten für die Leser-Kennungen abgeholt und verarbeitet. +Die Nachrichten-Verarbeitung erfolgt durch die übergebene `processMessage`-Funktion. + +Nachdem alle Nachrichten verarbeitet sind, werden die Transport-Reports zurückgegeben. + +Eine erfolgreich abgeholte/geschlossene Nachricht hat den Status `RED`, `YELLOW` oder `GREEN`. +Hierbei signalisiert `RED` einen kritischen Fehler, `YELLOW` einen Warnhinweis und `GREEN` eine fehlerfreie Verarbeitung. + +```mermaid +%% Empfangen von Nachrichten + sequenceDiagram + participant A as Nutzer*in + participant B as XTA-Client + participant C as XTA-Server + Note left of A: (1) XTA-Client erzeugen + activate A + Note left of A: (2) Nachrichten abholen + A->>B: fetchMessages + + activate B + loop Für jede Leser-Kennung + B->>C: checkAccountActive + activate C + C-->>B: + deactivate C + B->>C: getStatusList + activate C + C-->>B: + deactivate C + loop Für jede ungelesene Nachricht + B->>C: getMessage + activate C + Note left of B: Nachricht abholen + C-->>B: + deactivate C + B->>A: processMessage + Note left of A: (3) Nachricht verarbeiten + A-->>B: + B->>C: close + activate C + Note left of B: Nachricht schließen + C-->>B: + deactivate C + B->>C: getTransportReport + activate C + C-->>B: + deactivate C + end + end + B-->>A: + deactivate B + Note left of A: (4) Nachrichtenstatus prüfen + deactivate A +``` +<small>* Sollte `processMessage` bei der Verarbeitung eine *RuntimeException* werfen, wird `Nachricht schließen` übersprungen.</small> + +<small>Hinweis: Nicht geschlossene Nachrichten mit Status `OPEN` werden beim nächsten Aufruf von `fetchMessages` erneut bearbeitet.</small> + +**Beispielcode:** + +```java +// (1) XTA-Client erzeugen +var client = XtaClient.from(config); +// (2) Nachrichten abholen +var transportReports = client.fetchMessages(xtaMessage -> { + // (3) Nachricht verarbeiten +}); +// (4) Nachrichtenstatus prüfen +for (var transportReport : transportReports) { + assert transportReport.status() == XtaMessageStatus.GREEN; +} +``` + +## Konfiguration + +Der XTA-Client wird mit einer `XtaClientConfig` konfiguriert: +- XTA-Service-Port-URLs: `management`, `send` und `msgBox ` +- Client-Zertifikat der Autor-Kennung (für `sendMessage`) und der Leser-Kennungen (für `fetchMessages`) + - Leser-Client-Kennungen für `fetchMessages`. +- ... siehe [XtaClientConfig](src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java) + +**Beispielcode:** +```java +var config = XtaClientConfig.builder() + .managementServiceUrl("https://my-xta-server.de/MB_XTA-WS/XTA210managementPort.svc") + .sendServiceUrl("https://my-xta-server.de/MB_XTA-WS/XTA210sendPort.svc") + .msgBoxServiceUrl("https://my-xta-server.de/MB_XTA-WS/XTA210msgBoxPort.svc") + .clientCertKeystore(XtaClientConfig.KeyStore.builder() + .content(Files.toByteArray(new File("/path/to/client-cert.p12"))) + .type("PKCS12") + .password("keystore-password") + .bulid()) + .clientIdentifiers(List.of( + XtaIdentifier.builder() + .category("Generischer Antragsempfänger") + .value("gae:test-environment@ozg-cloud.de") + .build(), + XtaIdentifier.builder() + .category("Generischer Antragsempfänger") + .value("gae:dev-environment@ozg-cloud.de") + .build() + )) + .build(); +var client = XtaClient.from(config); +``` + +<small>Hinweis: Sollte die Root-CA des XTA-Servers nicht für das Ziel-System konfiguriert sein, muss zudem ein entsprechender `XtaClientConfig::trustStore` konfiguriert werden.</small> + +### Filterung von Nachrichten vor der Abholung +Um Nachrichten vor dem Abholen basierend auf `XtaMessageMetadata` zu filtern, sollte das optionale `Predicate<XtaMessageMetadata>` für `XtaClientConfig::isMessageSupported` konfiguriert werden. +Zum Beispiel kann auf diese Weise verhindert werden, dass nicht unterstützte Nachrichten, die unausweichlich zu einem Verarbeitungsfehler führen, vollständig heruntergeladen werden müssen. + + +## Referenzen +- Dieser XTA-Client basiert auf dem [XTA-Client-Beispielcode für XTA 2 Version 3 vom 21.07.2021](https://www.xoev.de/sixcms/media.php/13/XTA_Client_Version_3_20210721.zip) auffindbar unter [Hilfsmittel](https://www.xoev.de/xta/hilfsmittel). +- Die [XTA 2 Version 3.1 Spezifikation](https://www.xoev.de/sixcms/media.php/13/XTA_2_Version_3.1_Spezifikation_30112021.pdf) ist auffindbar unter [XTA 2 Version 3.1](https://www.xoev.de/xta/v3). -Mit dieser Bibliothek soll anhand des Kosit Beispielprojektes eine einfache Anbindung an die XTA API ermöglicht werden. -das ist ein toller gitea push mirror test \ No newline at end of file diff --git a/doku/Transportfestlegungen Generischer Antragsdienst.pdf b/doku/Transportfestlegungen Generischer Antragsdienst.pdf new file mode 100755 index 0000000000000000000000000000000000000000..3d0d632d76ab26beb10d662b86bec919a53d9603 Binary files /dev/null and b/doku/Transportfestlegungen Generischer Antragsdienst.pdf differ diff --git a/pom.xml b/pom.xml index a10e6b554ecaedaa58f9d6056c279432c30eee7a..aec12254cb40e53e45ac49a5a0bd4700211c79d8 100644 --- a/pom.xml +++ b/pom.xml @@ -154,7 +154,6 @@ <id>log4j2-plugin-processor</id> <goals> <goal>compile</goal> - <goal>testCompile</goal> </goals> <phase>process-classes</phase> <configuration> diff --git a/src/main/java/de/ozgcloud/xta/client/XtaClient.java b/src/main/java/de/ozgcloud/xta/client/XtaClient.java index 8d5d8b47a2af6bc8968e555b855185015b06d1e7..0ec945961695d4b0707093aa3d7556de9cf65ee7 100644 --- a/src/main/java/de/ozgcloud/xta/client/XtaClient.java +++ b/src/main/java/de/ozgcloud/xta/client/XtaClient.java @@ -13,9 +13,9 @@ import jakarta.validation.constraints.NotNull; import de.ozgcloud.xta.client.config.XtaClientConfig; import de.ozgcloud.xta.client.core.XtaClientService; import de.ozgcloud.xta.client.core.XtaExceptionHandler; -import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import de.ozgcloud.xta.client.exception.XtaClientException; import de.ozgcloud.xta.client.exception.XtaClientInitializationException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import de.ozgcloud.xta.client.model.XtaIdentifier; import de.ozgcloud.xta.client.model.XtaMessage; import de.ozgcloud.xta.client.model.XtaMessageMetaData; @@ -27,6 +27,22 @@ import lombok.Builder; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; +/** + * A client for sending and receiving messages via the XTA 2 protocol (version 3). + * + * <p> + * The client may be initialized with an {@link XtaClientConfig} configuration by {@link #from(XtaClientConfig)}. + * </p> + * + * Example: + * <pre> + * var client = XtaClient.from(config); + * var transportReport = client.sendMessage(message); + * var transportReports = client.fetchMessages(message -> { + * // process message + * }); + * </pre> + */ @RequiredArgsConstructor(access = AccessLevel.MODULE) @Builder(access = AccessLevel.MODULE) @Log4j2 @@ -38,10 +54,41 @@ public class XtaClient { static final String NO_MESSAGE_CLOSED_WARNING = "No message has been closed although more are pending. Try increasing max list items."; + /** + * Initialize a new {@link XtaClient} instance based on the given configuration. + * + * @param config The configuration for the client. + * @return The initialized client. + * @throws XtaClientInitializationException If the configuration is invalid or a configured keystore failed to initialize. + */ public static XtaClient from(XtaClientConfig config) throws XtaClientInitializationException { return XtaClientFactory.from(config).create(); } + /** + * Fetches messages for all client identifiers. + * + * <p> + * For each configured client identifier in {@link XtaClientConfig#getClientIdentifiers() clientIdentifiers}, checks if the client identifier is + * an active account, then lists its pending/unread messages. Next, uses the {@link XtaClientConfig#getIsMessageSupported() isMessageSupported} + * predicate to decide whether to fetch a listed message. Note that if no predicate is configured, all listed messages are fetched. + * </p> + * <p> + * For each fetched message, calls the given {@code processMessage}. If {@code processMessage} does not throw a runtime exception, closes the + * message, i.e., marks it as read. Then, fetches the transport report for the successfully or unsuccessfully processed message. Note that a + * transport is always returned for each processed message, unless a technical problem prevents fetching the transport report. + * </p> + * <p> + * A listing contains a maximum of {@link XtaClientConfig#getMaxListItems() maxListItems} messages. However, listing is repeated, as long as there + * are more messages pending and some message is closed successfully during the latest listing. + * </p> + * + * @param processMessage The consumer to process each fetched message. + * @return The transport reports for all fetched/processed messages. A message which has not been closed has an {@link XtaMessageStatus#OPEN OPEN} + * status. If a message has been closed, the status is either {@link XtaMessageStatus#GREEN GREEN}, {@link XtaMessageStatus#YELLOW YELLOW} or + * {@link XtaMessageStatus#RED RED}. + * @throws XtaClientException If a technical problem occurs while fetching messages. + */ public List<XtaTransportReport> fetchMessages(@NotNull Consumer<XtaMessage> processMessage) throws XtaClientException { try { return fetchMessagesRaw(processMessage); @@ -141,15 +188,37 @@ public class XtaClient { log.error("Processing of message '%s' failed! Not closing message.".formatted(messageId), exception); } - public XtaTransportReport sendMessage(@Valid XtaMessage message) throws XtaClientException { + /** + * Sends the given message. + * + * <p> + * Sends the message to the XTA server such that it may be asynchronously fetched by the reader. Author and reader of the message are given by the + * message metadata. + * </p> + * <p> + * First, checks whether the author refers to an active account. Then, checks that the service used by the message is available for the given + * reader. If both checks pass, sends the message to the reader. Finally, returns the transport report for the sent message. + * </p> + * <p> + * <b>Note:</b> message size and id are set by the server, and thus may initially be null. This also applies to the size of message and attachment + * files. + * </p> + * + * @param messageWithoutMessageId The XTA message to send without id and size. + * @return The transport report for the sent message. As long as no critical error occurred, indicated by status {@link XtaMessageStatus#RED RED}, + * the message the status is {@link XtaMessageStatus#OPEN OPEN}, until the reader closes the message. Moreover, the report contains the message + * metadata, including id and size values set by the server. + * @throws XtaClientException If a check fails or a technical problem occurs while sending the message. + */ + public XtaTransportReport sendMessage(@NotNull @Valid XtaMessage messageWithoutMessageId) throws XtaClientException { try { - return sendMessageRaw(message); + return sendMessageRaw(messageWithoutMessageId); } catch (RuntimeException exception) { throw exceptionHandler.deriveXtaClientException(exception); } } - XtaTransportReport sendMessageRaw(@Valid XtaMessage messageWithoutMessageId) { + XtaTransportReport sendMessageRaw(XtaMessage messageWithoutMessageId) { var metaData = messageWithoutMessageId.metaData(); throwExceptionIfAccountInactive(metaData.authorIdentifier()); throwExceptionIfServiceNotAvailable(metaData); diff --git a/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java b/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java index 14bdf0499aa8265eb430f31a09c266200a26e64e..b51cb0007e2c641cbb1be57e9f9d0cf8c001a865 100644 --- a/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java +++ b/src/main/java/de/ozgcloud/xta/client/config/XtaClientConfig.java @@ -17,53 +17,140 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; +/** + * Configuration of a {@link de.ozgcloud.xta.client.XtaClient}. + */ @RequiredArgsConstructor @Getter @Builder @ToString public class XtaClientConfig { + /** + * The reader client identifiers which are used to fetch messages. + * + * <p> + * Usually, in order to use the client identifiers, an according client certificate needs to be configured. + * </p> + * <p> + * <b>Note:</b> The identifier type is assumed to be {@code xoev}. + * </p> + */ @Builder.Default private final List<@Valid XtaIdentifier> clientIdentifiers = emptyList(); + /** + * This predicate may be used to filter listed messages before they are fetched. If null, all listed messages are fetched. + * + * <p> + * Note that, usually, all messages for a client identifier should be read, i.e., fetched and processed. However, it may be useful to filter + * messages before fetching them. For instance, to avoid downloading messages which would inevitably lead to a runtime exception during + * processing. + * </p> + */ @Builder.Default private final Predicate<XtaMessageMetaData> isMessageSupported = null; + /** + * The URL of the management service. + * + * For instance: {@code https://my-xta-server.de/MB_XTA-WS/XTA210managementPort.svc} + */ @NotBlank private final String managementServiceUrl; + /** + * The URL of the send service. + * + * For instance: {@code https://my-xta-server.de/MB_XTA-WS/XTA210sendPort.svc} + */ @NotBlank private final String sendServiceUrl; + /** + * The URL of the message box service. + * + * For instance: {@code https://my-xta-server.de/MB_XTA-WS/XTA210msgBoxPort.svc} + */ @NotBlank private final String msgBoxServiceUrl; + /** + * The number of message metadata items to request at a time. The default value is 50. + * + * <p> + * Note that reducing this limit may result in more listing calls. Furthermore, listing all messages may not be possible if the number of + * unprocessed messages reaches this limit, see {@linkplain de.ozgcloud.xta.client.XtaClient#fetchMessages fetchMessages}. + * </p> + */ @Positive @Builder.Default private final int maxListItems = 50; + /** + * The keystore used to authenticate this client to the server. + * + * <p> + * The keystore should contain a private key and a certificate chain which the server trusts. + * </p> + */ @Valid @Builder.Default private final KeyStore clientCertKeystore = null; + /** + * The truststore to authenticate the server to the client. + * + * <p> + * The truststore should contain a root certificate or a certificate chain of the server. May be null if the server certificate is trusted by the + * JVM. + * </p> + */ @Valid @Builder.Default private final KeyStore trustStore = null; + /** + * Whether to validate the XTA SOAP schema of the messages. Default is true. + * + * <p> + * This option is intended for testing purposes and turning it off is not recommended. + * </p> + */ @Builder.Default private final boolean schemaValidation = true; + + /** + * Whether to log SOAP requests. Default is false. + */ @Builder.Default private final boolean logSoapRequests = false; + + /** + * Whether to log SOAP responses. Default is false. + */ @Builder.Default private final boolean logSoapResponses = false; + /** + * Configuration for a keystore. Either for a client certificate or a trust store. + */ @RequiredArgsConstructor @Getter @Builder @ToString public static class KeyStore { + /** + * File content of the keystore file. + */ @NotNull private final byte[] content; + /** + * The type of the keystore. For instance, "PKCS12" or "JKS". + */ @NotBlank private final String type; + /** + * The password of the keystore. + */ @NotNull private final char[] password; } diff --git a/src/main/java/de/ozgcloud/xta/client/model/XtaFile.java b/src/main/java/de/ozgcloud/xta/client/model/XtaFile.java index 2561f45d22fcc92d3d4e729c78a40d741d470bc3..1617c7437c983d63d92bfe4abcffe90777af430a 100644 --- a/src/main/java/de/ozgcloud/xta/client/model/XtaFile.java +++ b/src/main/java/de/ozgcloud/xta/client/model/XtaFile.java @@ -10,6 +10,31 @@ import jakarta.validation.constraints.PositiveOrZero; import lombok.Builder; +/** + * Represents a file in the XTA context. + * + * <p> + * <small>Parameter documentation strings translated from <i>XTA-Webservice-Datentypen.xsd</i>.</small> + * </p> + * + * @param content A data handler of the file content. <p>This field is required.</p> + * @param contentType This attribute specifies the MIME type of the contained content, so it includes entries like {@code text/xml}, + * {@code text/plain}, {@code application/gzip}, or {@code application/pdf}. <p>This field is required as it is particularly + * important information (handled analogously in email).</p> + * @param contentDescription Description of the content, e.g., 'Offer' or 'Invoice'. <p>This field is optional.</p> + * @param encoding Character set that was used for encoding the content. <p>This field is optional.</p> + * @param name Filename of the data source, if the content was taken from a file. E.g.: For the transmission of xdomea messages, + * this attribute is mandatory. <p>This field is recommended as it is required for xdomea.</p> + * @param id Provides the ability to reference the content via, for example, a running number. <p>This field is optional and will be + * automatically set by the XTA server.</p> + * @param language Language of the file content, for instance, {@code en-US} or {@code de-DE}, see <a + * href="http://www.ietf.org/rfc/rfc3066.txt">RFC 3066</a>. <p>This field is optional.</p> + * @param size Size of the file content in bytes. + * + * <p> + * This field is optional and will be automatically set by the XTA server. + * </p> + */ @Builder(toBuilder = true) public record XtaFile( @NotNull DataHandler content, @@ -19,6 +44,6 @@ public record XtaFile( @NotBlank String name, @Nullable String id, @Nullable String language, - @PositiveOrZero BigInteger size + @Nullable @PositiveOrZero BigInteger size ) { } diff --git a/src/main/java/de/ozgcloud/xta/client/model/XtaIdentifier.java b/src/main/java/de/ozgcloud/xta/client/model/XtaIdentifier.java index 8ea86a7eeeaea8640df9f3a9a3fc623d6e98ffd9..30c50fda1f2c1e7232a4f62eb7a59ecb602c0753 100644 --- a/src/main/java/de/ozgcloud/xta/client/model/XtaIdentifier.java +++ b/src/main/java/de/ozgcloud/xta/client/model/XtaIdentifier.java @@ -5,6 +5,27 @@ import jakarta.validation.constraints.NotBlank; import lombok.Builder; +/** + * An XTA party identifier which identifies XTA (reader/author) clients or (sender/receiver) server. + * + * <p> + * This client library only uses client identifiers and assumes an identifier type of {@code xoev}. The client certificate authorizes the use of an + * identifier value as reader/author. Note that name and category may be freely chosen and serve as documentation of the actual value. + * </p> + * + * Example: + * <pre> + * var clientIdentifier = XtaIdentifier.builder() + * .value("gae:dev-environment@ozg-cloud.de") + * .category("Generischer Antragsempfänger") + * .name("OZG-Cloud Dev") + * .build(); + * </pre> + * + * @param name Name of the identifier. <p>This field is optional.</p> + * @param category Category of the identifier. <p>This field is optional.</p> + * @param value Value of the identifier. <p>This field is required.</p> + */ @Builder public record XtaIdentifier( @Nullable String name, diff --git a/src/main/java/de/ozgcloud/xta/client/model/XtaMessage.java b/src/main/java/de/ozgcloud/xta/client/model/XtaMessage.java index 10cf16eb90b271057680c386fde04af0956da777..bf636f23d196799bfcabcf14547db5a731e59616 100644 --- a/src/main/java/de/ozgcloud/xta/client/model/XtaMessage.java +++ b/src/main/java/de/ozgcloud/xta/client/model/XtaMessage.java @@ -7,6 +7,13 @@ import jakarta.validation.constraints.NotNull; import lombok.Builder; +/** + * An XTA message consisting of metadata, a message file and optional attachment files. + * + * @param metaData Metadata of the message <p>This field is required.</p> + * @param messageFile Main message of the message <p>This field is required.</p> + * @param attachmentFiles Attachment files of the message <p>Note that this list should always be empty for xdomea messages.</p> + */ @Builder(toBuilder = true) public record XtaMessage( @NotNull @Valid XtaMessageMetaData metaData, diff --git a/src/main/java/de/ozgcloud/xta/client/model/XtaMessageAndTransportReport.java b/src/main/java/de/ozgcloud/xta/client/model/XtaMessageAndTransportReport.java deleted file mode 100644 index 6fed361806962dbb1f57c2a747ce65839cd79d4d..0000000000000000000000000000000000000000 --- a/src/main/java/de/ozgcloud/xta/client/model/XtaMessageAndTransportReport.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.ozgcloud.xta.client.model; - -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; - -import lombok.Builder; - -@Builder -public record XtaMessageAndTransportReport( - @NotNull @Valid XtaMessage message, - @NotNull @Valid XtaTransportReport transportReport -) { -} diff --git a/src/main/java/de/ozgcloud/xta/client/model/XtaMessageMetaData.java b/src/main/java/de/ozgcloud/xta/client/model/XtaMessageMetaData.java index 4ff59de99851b17542b682b0efe5b92b80b5ee67..a9b83320e520c6a718ebd7815662fc477eb3dc20 100644 --- a/src/main/java/de/ozgcloud/xta/client/model/XtaMessageMetaData.java +++ b/src/main/java/de/ozgcloud/xta/client/model/XtaMessageMetaData.java @@ -10,6 +10,24 @@ import jakarta.validation.constraints.PositiveOrZero; import lombok.Builder; +/** + * Represents the metadata of an XTA message. + * + * <p> + * <small>Parameter documentation strings partially from <i>OSCI_MessageMetaData_V2.02.xsd</i>.</small> + * </p> + * + * @param service Distinct service in a certain business scenario context; in the XÖV context this is the "Dienste URI" (service URI). <p>This field is required.</p> + * @param businessScenarioCode Domain qualifier, e.g. Meldewesen, XVergabe... <p>This field is required.</p> + * @param businessScenarioListUri Code list URI of the business scenario. <p>If {@code null}, {@link XtaMessageMetaData#businessScenarioCode} refers to an undefined business scenario. Otherwise, it is a defined scenario which additionally requires {@link XtaMessageMetaData#businessScenarioListVersionId}.</p> + * @param businessScenarioListVersionId code list version ID of the business scenario. <p>Required if {@link XtaMessageMetaData#businessScenarioListUri} is not {@code null}.</p> + * @param messageTypeCode Code of the message type. <p>For instance, <i>Geschaeftsgang.Geschaeftsgang.0201</i> with business scenario <i>XDOMEAGAD_DATA</i>.</p> <p>This field is required.</p> + * @param messageTypePayloadSchema Payload schema of the message type. <p>For instance, <i>urn:xoev-de:xdomea:schema:2.4.0</i> with code <i>Geschaeftsgang.Geschaeftsgang.0201</i> and business scenario <i>XDOMEAGAD_DATA</i>.</p> <p>This field is required.</p> + * @param messageId Unique identifier of the message. <p>The ID is generated by the server and thus not required when sending.</p> + * @param authorIdentifier Identifier of the author of the message. <p>This field is required.</p> + * @param readerIdentifier Identifier of the reader of the message. <p>This field is required.</p> + * @param messageSize Size of the message in bytes. <p>The size is determined by the server an thus not required when sending.</p> + */ @Builder(toBuilder = true) public record XtaMessageMetaData( @NotBlank String service, diff --git a/src/main/java/de/ozgcloud/xta/client/model/XtaMessageStatus.java b/src/main/java/de/ozgcloud/xta/client/model/XtaMessageStatus.java index 4f43119f49063ff54013f81917ced31118d92655..ccf1baf22a48d48d4fd8d0a3427f67dabc4b204f 100644 --- a/src/main/java/de/ozgcloud/xta/client/model/XtaMessageStatus.java +++ b/src/main/java/de/ozgcloud/xta/client/model/XtaMessageStatus.java @@ -3,12 +3,27 @@ package de.ozgcloud.xta.client.model; import lombok.Getter; import lombok.RequiredArgsConstructor; +/** + * Message status of a XTA message. The status is determined by the XTA sender/receiver server. + */ @RequiredArgsConstructor @Getter public enum XtaMessageStatus { + /** + * Status for a message that is open, i.e., has not been closed. + */ OPEN(0), + /** + * Status for a message that has been closed and has no errors or warnings. + */ GREEN(1), + /** + * Status for a message that has been closed and has warnings but no errors. + */ YELLOW(2), + /** + * Status for a message that has been closed and has errors. + */ RED(3); private final Integer code; diff --git a/src/main/java/de/ozgcloud/xta/client/model/XtaTransportReport.java b/src/main/java/de/ozgcloud/xta/client/model/XtaTransportReport.java index c768d2a3bf7e5fe1ef6c88ce46fc474b32829358..13dca75c777988fc90015225f78cb8384b96b137 100644 --- a/src/main/java/de/ozgcloud/xta/client/model/XtaTransportReport.java +++ b/src/main/java/de/ozgcloud/xta/client/model/XtaTransportReport.java @@ -7,6 +7,13 @@ import jakarta.validation.constraints.NotNull; import lombok.Builder; +/** + * A transport report of a XTA message. + * + * @param metaData Metadata of the message. + * @param reportTime Time of the report. + * @param status Status of the message at the time of {@link XtaTransportReport#reportTime}. + */ @Builder public record XtaTransportReport( @NotNull @Valid XtaMessageMetaData metaData, diff --git a/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreator.java b/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreator.java index 05c1949899aef4596294624f72055859fd210620..13b5ec07e60a9d32a011ca18e90472f6a1fea461 100644 --- a/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreator.java +++ b/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreator.java @@ -10,8 +10,8 @@ import javax.xml.parsers.DocumentBuilder; import org.w3c.dom.Document; import org.xml.sax.SAXException; -import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.xta.client.exception.XtaClientException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import de.ozgcloud.xta.client.model.XtaFile; import de.ozgcloud.xta.client.model.XtaMessage; import de.ozgcloud.xta.client.model.XtaMessageMetaData; @@ -22,6 +22,30 @@ import de.ozgcloud.xta.client.xdomea.reader.ZipFileEntryReader; import lombok.Builder; import lombok.RequiredArgsConstructor; +/** + * Creator of {@link XtaMessage}s for Xdomea. + * + * <p> + * The XdomeaXtaMessageCreator is used to create an {@link XtaMessage} for an Xdomea zip file. The resulting message may be send via the + * {@link de.ozgcloud.xta.client.XtaClient XtaClient}. + * </p> + * + * + * Example: + * <pre> + * var xtaMessageCreator = XdomeaXtaMessageCreator.createInstance(); + * var zipFileName = "d5be7468-e620-4126-a40e-61a7f9b46345_Geschaeftsgang.Geschaeftsgang.0201.zip"; + * var zipFileContentDataHandler = new DataHandler(new FileDataSource("/path/to/" + zipFileName)); + * var xdomeaXtaFile = XtaFile.builder() + * .name(zipFileName) + * .content(zipFileContentDataHandler) + * .contentType("application/zip") + * .build(); + * var xtaMessage = xtaMessageCreator.createMessage(xdomeaXtaFile); + * + * XtaClient.from(...).sendMessage(xtaMessage); + * </pre> + */ @Builder @RequiredArgsConstructor public class XdomeaXtaMessageCreator { @@ -36,6 +60,23 @@ public class XdomeaXtaMessageCreator { return XdomeaXtaMessageCreatorFactory.createInstance().create(); } + /** + * Creates an {@link XtaMessage} from the given xdomea zip file. + * + * <p> + * The zip file has to follow the XTA/Xdomea generic transport specification. According to the specification, only the Xdomea messages of type + * 0201, 0401 and 0402 are supported. + * </p> + * + * <p> + * <small>The XTA/Xdomea generic transport specification is unofficially available as <i>"Transportfestlegungen Generischer + * Antragsdienst.pdf"</i>.</small> + * </p> + * + * @param xdomeaZipFile the xdomea zip file + * @return the xta message with metadata derived from the xdomea zip file + * @throws XtaClientException if no valid metadata could be derived from the xdomea zip file. + */ public XtaMessage createMessage(XtaFile xdomeaZipFile) throws XtaClientException { return XtaMessage.builder() .metaData(deriveValidMetaData(xdomeaZipFile)) @@ -50,7 +91,7 @@ public class XdomeaXtaMessageCreator { var messageMetadataData = metadataMapper.mapXtaMessageMetadata(xdomeaXmlValues); metaDataValidator.validate(xdomeaZipFile, xdomeaXmlValues, messageMetadataData); return messageMetadataData; - } catch (TechnicalException e) { + } catch (XtaClientRuntimeException e) { throw new XtaClientException("Failed to derive valid message metadata from xdomea document!", e); } } @@ -72,7 +113,7 @@ public class XdomeaXtaMessageCreator { try { return documentBuilder.parse(inputStream); } catch (SAXException | IOException e) { - throw new TechnicalException("Failed to parse xml document!", e); + throw new XtaClientRuntimeException("Failed to parse xml document!", e); } } diff --git a/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorFactory.java b/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorFactory.java index 3b6ff6aba506a706aa6c09cc66c6d9a87e1f66bb..914f9e3d1d9b9890de3a18c231b654fc7df065ab 100644 --- a/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorFactory.java +++ b/src/main/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorFactory.java @@ -22,7 +22,7 @@ import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import de.ozgcloud.xta.client.xdomea.mapper.MetadataMapper; import de.ozgcloud.xta.client.xdomea.reader.XdomeaValueReaderFactory; import de.ozgcloud.xta.client.xdomea.reader.ZipFileEntryReader; @@ -82,7 +82,7 @@ public class XdomeaXtaMessageCreatorFactory { xPathFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE); return xPathFactory; } catch (XPathFactoryConfigurationException e) { - throw new TechnicalException("Failed to configure xpath factory!", e); + throw new XtaClientRuntimeException("Failed to configure xpath factory!", e); } } @@ -126,7 +126,7 @@ public class XdomeaXtaMessageCreatorFactory { schemaFactory.setResourceResolver(createResourceResolver()); return schemaFactory.newSchema(createSchemaStreamSources()); } catch (SAXException e) { - throw new TechnicalException("Failed to initialize schema", e); + throw new XtaClientRuntimeException("Failed to initialize schema", e); } } @@ -140,7 +140,7 @@ public class XdomeaXtaMessageCreatorFactory { documentBuilder.setErrorHandler(errorHandler); return documentBuilder; } catch (ParserConfigurationException e) { - throw new TechnicalException("Failed to configure document builder!", e); + throw new XtaClientRuntimeException("Failed to configure document builder!", e); } } diff --git a/src/main/java/de/ozgcloud/xta/client/xdomea/mapper/MetadataMapper.java b/src/main/java/de/ozgcloud/xta/client/xdomea/mapper/MetadataMapper.java index 94ccf0948d49d4fec6b3919310cba71a756a622c..5d02ec81a0186c44be01e8bbdc098d6bb4505630 100644 --- a/src/main/java/de/ozgcloud/xta/client/xdomea/mapper/MetadataMapper.java +++ b/src/main/java/de/ozgcloud/xta/client/xdomea/mapper/MetadataMapper.java @@ -5,7 +5,7 @@ import org.mapstruct.Mapping; import org.mapstruct.Named; import org.mapstruct.ReportingPolicy; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import de.ozgcloud.xta.client.model.XtaIdentifier; import de.ozgcloud.xta.client.model.XtaMessageMetaData; import de.ozgcloud.xta.client.xdomea.reader.XdomeaXmlValues; @@ -16,8 +16,10 @@ import de.ozgcloud.xta.client.xdomea.reader.XdomeaXmlValues; public interface MetadataMapper { String CODE_0201 = "0201"; String CODE_0401 = "0401"; + String CODE_0402 = "0402"; String MESSAGE_TYPE_CODE_0201 = "Geschaeftsgang.Geschaeftsgang.0201"; String MESSAGE_TYPE_CODE_0401 = "Abgabe.Abgabe.0401"; + String MESSAGE_TYPE_CODE_0402 = "Abgabe.ImportBestaetigen.0402"; String AUTHOR_ID_PREFIX = "gad"; String AUTHOR_ID_CATEGORY = "Generischer Antragsdienst"; @@ -49,8 +51,8 @@ public interface MetadataMapper { default String getServiceByMessageTypeCode(String messageTypeCode) { return switch (messageTypeCode) { case CODE_0201 -> SERVICE_XDOMEA_240; - case CODE_0401 -> SERVICE_XDOMEA_300; - default -> throw new TechnicalException("Unknown messageTypeCode: " + messageTypeCode); + case CODE_0401, CODE_0402 -> SERVICE_XDOMEA_300; + default -> throw new XtaClientRuntimeException("Unknown messageTypeCode: " + messageTypeCode); }; } @@ -59,7 +61,8 @@ public interface MetadataMapper { return switch (messageTypeCode) { case CODE_0201 -> MESSAGE_TYPE_CODE_0201; case CODE_0401 -> MESSAGE_TYPE_CODE_0401; - default -> throw new TechnicalException("Unknown messageTypeCode: " + messageTypeCode); + case CODE_0402 -> MESSAGE_TYPE_CODE_0402; + default -> throw new XtaClientRuntimeException("Unknown messageTypeCode: " + messageTypeCode); }; } @@ -67,8 +70,8 @@ public interface MetadataMapper { default String getMessageTypePayloadSchemaByMessageTypeCode(String messageTypeCode) { return switch (messageTypeCode) { case CODE_0201 -> MESSAGE_TYPE_PAYLOAD_SCHEMA_XDOMEA_240; - case CODE_0401 -> MESSAGE_TYPE_PAYLOAD_SCHEMA_XDOMEA_300; - default -> throw new TechnicalException("Unknown messageTypeCode: " + messageTypeCode); + case CODE_0401, CODE_0402 -> MESSAGE_TYPE_PAYLOAD_SCHEMA_XDOMEA_300; + default -> throw new XtaClientRuntimeException("Unknown messageTypeCode: " + messageTypeCode); }; } diff --git a/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReader.java b/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReader.java index 642bff51e49b4c7ff2b3c2d8f7cbefc362dc4b67..8019eef8967bf0b9a4eed21c6a9090e5e15bac4a 100644 --- a/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReader.java +++ b/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReader.java @@ -11,7 +11,7 @@ import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -34,7 +34,7 @@ public class XmlValueReader { XPathConstants.NODESET ); } catch (XPathExpressionException e) { - throw new TechnicalException("Failed to execute xpath search!", e); + throw new XtaClientRuntimeException("Failed to execute xpath search!", e); } } diff --git a/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReaderFactory.java b/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReaderFactory.java index 5f28091a231a036a4dfefd2b6e9a08883199e0c4..24b7b95913a0190d6a1210c42b83ef641cd7d563 100644 --- a/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReaderFactory.java +++ b/src/main/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReaderFactory.java @@ -4,7 +4,7 @@ import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -19,7 +19,7 @@ public class XmlValueReaderFactory { try { return xPathFactory.newXPath().compile(textXpathString); } catch (XPathExpressionException e) { - throw new TechnicalException("Failed to compile xpath expression!", e); + throw new XtaClientRuntimeException("Failed to compile xpath expression!", e); } } diff --git a/src/main/java/de/ozgcloud/xta/client/xdomea/reader/ZipFileEntryReader.java b/src/main/java/de/ozgcloud/xta/client/xdomea/reader/ZipFileEntryReader.java index 8b0df0b9cc40098cd285e228fed1895c0a66e862..de117ea42d507b860d7d69f77d84f289289ed41f 100644 --- a/src/main/java/de/ozgcloud/xta/client/xdomea/reader/ZipFileEntryReader.java +++ b/src/main/java/de/ozgcloud/xta/client/xdomea/reader/ZipFileEntryReader.java @@ -10,7 +10,7 @@ import java.util.zip.ZipInputStream; import jakarta.activation.DataHandler; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import lombok.Builder; import lombok.RequiredArgsConstructor; @@ -27,9 +27,9 @@ public class ZipFileEntryReader { } zipInputStream.closeEntry(); } - throw new TechnicalException("Failed to find zip entry '%s'!".formatted(entryName)); + throw new XtaClientRuntimeException("Failed to find zip entry '%s'!".formatted(entryName)); } catch (IOException e) { - throw new TechnicalException("Failed reading zip file!", e); + throw new XtaClientRuntimeException("Failed reading zip file!", e); } } @@ -43,7 +43,7 @@ public class ZipFileEntryReader { } return entryNames; } catch (IOException e) { - throw new TechnicalException("Failed reading zip file!", e); + throw new XtaClientRuntimeException("Failed reading zip file!", e); } } } diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java index 64681cfe9abead1d25b6f0d8626241b0f90244a7..0a244ea890e783196ba7be4f7c85ccbd06d93689 100644 --- a/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java +++ b/src/test/java/de/ozgcloud/xta/client/XtaClientITCase.java @@ -30,7 +30,7 @@ import lombok.SneakyThrows; class XtaClientITCase { @RegisterExtension - static final XtaTestServerSetupExtension XTA_TEST_SERVER_SETUP_EXTENSION = new XtaTestServerSetupExtension(); + private static final XtaTestServerSetupExtension XTA_TEST_SERVER_SETUP_EXTENSION = new XtaTestServerSetupExtension(); static final int TWO_MAX_LIST_ITEMS = 2; private XtaClient silentTestClient; diff --git a/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java b/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java index 0ceac4857e86725204502ed1d2828a864c891ce7..8878a3f753dda96d394db1fc801f0462e33c3dad 100644 --- a/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java +++ b/src/test/java/de/ozgcloud/xta/client/XtaClientRemoteITCase.java @@ -11,7 +11,6 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Stream; -import org.junit.Ignore; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -44,11 +43,10 @@ import lombok.extern.slf4j.Slf4j; "This test requires the path KOP_SH_KIEL_{DEV,TEST}_PATH and password KOP_SH_KIEL_{DEV,TEST}_PASSWORD of KOP_SH_KIEL_DEV.p12 and OZG-CLOUD_SH_KIEL_TEST.pfx. " + "Additionally, the endpoint of the DEV-xta-server at li33-0005.dp.dsecurecloud.de must be reachable." ) -@Ignore class XtaClientRemoteITCase { @RegisterExtension - static final XtaRemoteServerSetupExtension XTA_REMOTE_SERVER_SETUP_EXTENSION = new XtaRemoteServerSetupExtension(); + private static final XtaRemoteServerSetupExtension XTA_REMOTE_SERVER_SETUP_EXTENSION = new XtaRemoteServerSetupExtension(); private static final XdomeaXtaMessageCreator XDOMEA_XTA_MESSAGE_CREATOR = XdomeaXtaMessageCreator.createInstance(); @@ -62,6 +60,8 @@ class XtaClientRemoteITCase { private Consumer<XtaMessage> processMessageDummy; private Predicate<XtaMessageMetaData> isSupportedDummy; + private List<String> sendMessageIds; + @BeforeEach @SneakyThrows void setup() { @@ -76,7 +76,6 @@ class XtaClientRemoteITCase { devClient = XTA_REMOTE_SERVER_SETUP_EXTENSION.getDevClient(); silentDevClient = XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentDevClient(); - // Fail if any message pending, to ensure that we do not clear existing messages in the DEV environment failIfAnyMessagePending(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentDevClientConfig(), DEV_READER_CLIENT_IDENTIFIER); failIfAnyMessagePending(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentTestClientConfig(), TEST_READER_CLIENT_IDENTIFIER); @@ -89,8 +88,8 @@ class XtaClientRemoteITCase { } private void closeMessagesForAllReaders() { - closeAllMessages(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentDevClientConfig(), DEV_READER_CLIENT_IDENTIFIER); - closeAllMessages(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentTestClientConfig(), TEST_READER_CLIENT_IDENTIFIER); + closeMessagesById(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentDevClientConfig(), DEV_READER_CLIENT_IDENTIFIER, sendMessageIds); + closeMessagesById(XTA_REMOTE_SERVER_SETUP_EXTENSION.getSilentTestClientConfig(), TEST_READER_CLIENT_IDENTIFIER, sendMessageIds); } @DisplayName("fetch messages") @@ -98,7 +97,6 @@ class XtaClientRemoteITCase { class TestFetchMessages { private List<XtaMessage> sendMessages; - private List<String> sendMessageIds; @BeforeEach void setup() { @@ -258,10 +256,23 @@ class XtaClientRemoteITCase { void shouldReturn(String messageLabel) { XtaMessage xtaMessage = createXdomeaMessage(loadMessage(messageLabel).messageFile()); - var result = testClient.sendMessage(xtaMessage); + var result = sendMessage(xtaMessage); assertThat(result.status()).isEqualTo(XtaMessageStatus.OPEN); } + + @SneakyThrows + private XtaTransportReport sendMessage(XtaMessage xtaMessage) { + var report = testClient.sendMessage(xtaMessage); + recordMessageIdForCleanup(report); + return report; + } + + private void recordMessageIdForCleanup(XtaTransportReport report) { + var messageId = report.metaData().messageId(); + assertThat(messageId).isNotNull(); + sendMessageIds = List.of(messageId); + } } private XtaMessage createMessage(String messageLabel, XtaIdentifier author, XtaIdentifier reader) { diff --git a/src/test/java/de/ozgcloud/xta/client/extension/Patch.java b/src/test/java/de/ozgcloud/xta/client/extension/Patch.java index d527b0f0325b879aecc1d4fef0bb7067e6e42b8e..425cdfa9edf8333f59187e2df8d31861aaffe88a 100644 --- a/src/test/java/de/ozgcloud/xta/client/extension/Patch.java +++ b/src/test/java/de/ozgcloud/xta/client/extension/Patch.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; public record Patch( String filePath, @@ -30,8 +30,8 @@ public record Patch( lineOffset = patchResult.lineOffset(); } lines.forEachRemaining(patchedContent::append); - } catch (TechnicalException exception) { - throw new TechnicalException("Error applying patch! filePath=" + filePath, exception); + } catch (XtaClientRuntimeException exception) { + throw new XtaClientRuntimeException("Error applying patch! filePath=" + filePath, exception); } return patchedContent.toString(); } diff --git a/src/test/java/de/ozgcloud/xta/client/extension/PatchHunk.java b/src/test/java/de/ozgcloud/xta/client/extension/PatchHunk.java index 912c39baa0c9688d2797813cb95a4142af9c8b1f..4c3c4268a292a06cdff6b692cbb898d157cb2618 100644 --- a/src/test/java/de/ozgcloud/xta/client/extension/PatchHunk.java +++ b/src/test/java/de/ozgcloud/xta/client/extension/PatchHunk.java @@ -4,7 +4,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; public record PatchHunk( int contextStartLineNumber, @@ -27,7 +27,7 @@ public record PatchHunk( var contextLine = lines.next(); lineOffset++; if (!hunkLine.line.equals(contextLine)) { - throw new TechnicalException( + throw new XtaClientRuntimeException( "Unexpected context line! expected='" + hunkLine.line + "' actual='" + contextLine + "', lineOffset=" + lineOffset); } patchedLines.add(contextLine); @@ -37,7 +37,7 @@ public record PatchHunk( var removeLine = lines.next(); lineOffset++; if (!hunkLine.line.equals(removeLine)) { - throw new TechnicalException( + throw new XtaClientRuntimeException( "Unexpected remove line! expected='" + hunkLine.line + "' actual='" + removeLine + "', lineOffset=" + lineOffset); } } @@ -49,7 +49,7 @@ public record PatchHunk( private int skipToContextStart(Iterator<String> lines, int lineOffset, List<String> patchedLines) { for (; lineOffset < contextStartLineNumber - 1; lineOffset++) { if (!lines.hasNext()) { - throw new TechnicalException("Unexpected end of target file! lineOffset=" + lineOffset); + throw new XtaClientRuntimeException("Unexpected end of target file! lineOffset=" + lineOffset); } patchedLines.add(lines.next()); } @@ -90,14 +90,14 @@ public record PatchHunk( var sourceLineOffset = 0; while (sourceLineOffset < header.sourceLineCount()) { if (!lines.hasNext()) { - throw new TechnicalException("Unexpected end of patch hunk! sourceLineOffset=" + sourceLineOffset + ", header=" + header); + throw new XtaClientRuntimeException("Unexpected end of patch hunk! sourceLineOffset=" + sourceLineOffset + ", header=" + header); } var line = lines.next(); var operation = switch (line.substring(0, 1)) { case " " -> PatchOperation.CONTEXT; case "+" -> PatchOperation.ADD; case "-" -> PatchOperation.REMOVE; - default -> throw new TechnicalException( + default -> throw new XtaClientRuntimeException( "Unexpected line in patch hunk! line=" + line + "sourceLineOffset=" + sourceLineOffset + ", header=" + header); }; sourceLineOffset += switch (operation) { diff --git a/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java b/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java index 0432d3445846171a7908c47377e0d1b32baa48be..cdd531f78bf541920f2d62089efe29bd83b62f36 100644 --- a/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java +++ b/src/test/java/de/ozgcloud/xta/client/extension/XtaServerSetupExtensionTestUtil.java @@ -119,6 +119,12 @@ public class XtaServerSetupExtensionTestUtil { var messageIds = result.messages().stream() .map(XtaMessageMetaData::messageId) .toList(); + closeMessagesById(config, clientId, messageIds); + } + + @SneakyThrows + public static void closeMessagesById(XtaClientConfig config, XtaIdentifier clientId, List<String> messageIds) { + var wrappedService = createWrappedService(config); for (var messageId : messageIds) { wrappedService.close(messageId, clientId); } diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorFactoryTest.java b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorFactoryTest.java index 723de052ef5cbc4d9796fe8179329e33b70045e7..2dac4c546741642d83ca57f968ef6b8362cb6a97 100644 --- a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorFactoryTest.java +++ b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorFactoryTest.java @@ -33,7 +33,7 @@ import org.w3c.dom.ls.LSResourceResolver; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import de.ozgcloud.xta.client.xdomea.mapper.MetadataMapper; import de.ozgcloud.xta.client.xdomea.reader.XdomeaValueReader; import de.ozgcloud.xta.client.xdomea.reader.XdomeaValueReaderFactory; @@ -164,7 +164,7 @@ class XdomeaXtaMessageCreatorFactoryTest { doThrow(parserConfigurationException).when(documentBuilderFactory).setFeature(DISALLOW_DOCTYPE_DECL_FEATURE_URL, true); assertThatThrownBy(() -> factory.create()) - .isInstanceOf(TechnicalException.class) + .isInstanceOf(XtaClientRuntimeException.class) .hasCause(parserConfigurationException); } @@ -222,14 +222,14 @@ class XdomeaXtaMessageCreatorFactoryTest { verify(schemaFactory).setResourceResolver(resolver); } - @DisplayName("should throw technical initialization exception") + @DisplayName("should throw runtime initialization exception") @Test @SneakyThrows void shouldThrowTechnicalInitializationException() { doThrow(new SAXException()).when(schemaFactory).newSchema(schemaSources); assertThatThrownBy(() -> factory.createSchema()) - .isInstanceOf(TechnicalException.class); + .isInstanceOf(XtaClientRuntimeException.class); } } diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorITCase.java b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorITCase.java index 7ff3ffd6754a4682db3cf3c660134f77127a6ee0..84c3eea862fd6197ddae096f5f2171cb61f31cd2 100644 --- a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorITCase.java +++ b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorITCase.java @@ -22,7 +22,7 @@ class XdomeaXtaMessageCreatorITCase { @BeforeEach void setup() { - creator = XdomeaXtaMessageCreatorFactory.createInstance().create(); + creator = XdomeaXtaMessageCreator.createInstance(); } @DisplayName("create message") diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorTest.java b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorTest.java index a183007f589a4083b82296c8c9331825a434c727..48864bcbe17e4277fb2ba5837284dc902c2eca2f 100644 --- a/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorTest.java +++ b/src/test/java/de/ozgcloud/xta/client/xdomea/XdomeaXtaMessageCreatorTest.java @@ -19,8 +19,8 @@ import org.mockito.Mock; import org.mockito.Spy; import org.w3c.dom.Document; -import de.ozgcloud.common.errorhandling.TechnicalException; import de.ozgcloud.xta.client.exception.XtaClientException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import de.ozgcloud.xta.client.factory.XdomeaXmlValuesTestFactory; import de.ozgcloud.xta.client.factory.XtaFileTestFactory; import de.ozgcloud.xta.client.factory.XtaMessageMetaDataTestFactory; @@ -111,7 +111,7 @@ class XdomeaXtaMessageCreatorTest { private XtaMessageMetaData metaData; @Mock - private TechnicalException technicalException; + private XtaClientRuntimeException runtimeException; @BeforeEach @SneakyThrows @@ -153,7 +153,7 @@ class XdomeaXtaMessageCreatorTest { @DisplayName("should throw client exception") @Test void shouldThrowClientException() { - doThrow(technicalException).when(metadataMapper).mapXtaMessageMetadata(xdomeaXmlValues); + doThrow(runtimeException).when(metadataMapper).mapXtaMessageMetadata(xdomeaXmlValues); assertThatThrownBy(() -> creator.deriveValidMetaData(xdomeaZipFile)) .isInstanceOf(XtaClientException.class); diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/mapper/MetadataMapperTest.java b/src/test/java/de/ozgcloud/xta/client/xdomea/mapper/MetadataMapperTest.java index 46ed9f9fbfd4c6e95439562023ea9c723dc7e1d5..95529c12d0f7468a1ce02929b200af5769ecde59 100644 --- a/src/test/java/de/ozgcloud/xta/client/xdomea/mapper/MetadataMapperTest.java +++ b/src/test/java/de/ozgcloud/xta/client/xdomea/mapper/MetadataMapperTest.java @@ -11,7 +11,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mapstruct.factory.Mappers; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import de.ozgcloud.xta.client.factory.XdomeaXmlValuesTestFactory; import de.ozgcloud.xta.client.model.XtaMessageMetaData; @@ -31,6 +31,14 @@ class MetadataMapperTest { assertThat(metadata.service()).isEqualTo(SERVICE_XDOMEA_300); } + @DisplayName("should map service 0402") + @Test + void shouldMapService0402() { + var metadata = doMapping(CODE_0402); + + assertThat(metadata.service()).isEqualTo(SERVICE_XDOMEA_300); + } + @DisplayName("should map service 0201") @Test void shouldMapService0201() { @@ -74,6 +82,14 @@ class MetadataMapperTest { assertThat(metadata.messageTypeCode()).isEqualTo(MESSAGE_TYPE_CODE_0401); } + @DisplayName("should map message type code 0402") + @Test + void shouldMapMessageTypeCode0402() { + var metadata = doMapping(CODE_0402); + + assertThat(metadata.messageTypeCode()).isEqualTo(MESSAGE_TYPE_CODE_0402); + } + @DisplayName("should map message type code 0201") @Test void shouldMapMessageTypeCode0201() { @@ -90,6 +106,14 @@ class MetadataMapperTest { assertThat(metadata.messageTypePayloadSchema()).isEqualTo(MESSAGE_TYPE_PAYLOAD_SCHEMA_XDOMEA_300); } + @DisplayName("should map message type payload schema 0402") + @Test + void shouldMapMessageTypePayloadSchema0402() { + var metadata = doMapping(CODE_0402); + + assertThat(metadata.messageTypePayloadSchema()).isEqualTo(MESSAGE_TYPE_PAYLOAD_SCHEMA_XDOMEA_300); + } + @DisplayName("should map message type payload schema 0201") @Test void shouldMapMessageTypePayloadSchema0201() { @@ -100,7 +124,7 @@ class MetadataMapperTest { @DisplayName("should map author identifier value") @ParameterizedTest - @ValueSource(strings = { CODE_0201, CODE_0401 }) + @ValueSource(strings = { CODE_0201, CODE_0401, CODE_0402 }) void shouldMapAuthorIdentifierValue(String code) { var metadata = doMapping(code); @@ -109,7 +133,7 @@ class MetadataMapperTest { @DisplayName("should map author identifier category") @ParameterizedTest - @ValueSource(strings = { CODE_0201, CODE_0401 }) + @ValueSource(strings = { CODE_0201, CODE_0401, CODE_0402 }) void shouldMapAuthorIdentifierCategory(String code) { var metadata = doMapping(code); @@ -118,7 +142,7 @@ class MetadataMapperTest { @DisplayName("should map reader identifier value") @ParameterizedTest - @ValueSource(strings = { CODE_0201, CODE_0401 }) + @ValueSource(strings = { CODE_0201, CODE_0401, CODE_0402 }) void shouldMapReaderIdentifierValue(String code) { var metadata = doMapping(code); @@ -127,18 +151,18 @@ class MetadataMapperTest { @DisplayName("should map reader identifier category") @ParameterizedTest - @ValueSource(strings = { CODE_0201, CODE_0401 }) + @ValueSource(strings = { CODE_0201, CODE_0401, CODE_0402 }) void shouldMapReaderIdentifierCategory(String code) { var metadata = doMapping(code); assertThat(metadata.readerIdentifier().category()).isEqualTo(READER_ID_CATEGORY); } - @DisplayName("should throw technical exception with unknown message type code") + @DisplayName("should throw xta client runtime exception with unknown message type code") @Test - void shouldThrowTechnicalExceptionWithUnknownMessageTypeCode() { + void shouldThrowXtaClientRuntimeExceptionWithUnknownMessageTypeCode() { assertThatThrownBy(() -> doMapping("unknown")) - .isInstanceOf(TechnicalException.class); + .isInstanceOf(XtaClientRuntimeException.class); } private XtaMessageMetaData doMapping(String code) { diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReaderFactoryTest.java b/src/test/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReaderFactoryTest.java index b848a3aea0f792d85c9676b501b53c6b7037eae9..ddce12f863c0685cb079da01129a459ab710a22e 100644 --- a/src/test/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReaderFactoryTest.java +++ b/src/test/java/de/ozgcloud/xta/client/xdomea/reader/XmlValueReaderFactoryTest.java @@ -14,7 +14,7 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.mockito.Mock; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; import lombok.SneakyThrows; class XmlValueReaderFactoryTest { @@ -66,14 +66,14 @@ class XmlValueReaderFactoryTest { .build()); } - @DisplayName("should throw technical exception") + @DisplayName("should throw xta client runtime exception") @Test @SneakyThrows - void shouldThrowTechnicalException() { + void shouldThrowXtaClientRuntimeException() { when(xpath.compile(textXpathString)).thenThrow(exception); assertThatThrownBy(() -> factory.create()) - .isInstanceOf(TechnicalException.class); + .isInstanceOf(XtaClientRuntimeException.class); } } diff --git a/src/test/java/de/ozgcloud/xta/client/xdomea/reader/ZipFileEntryReaderTest.java b/src/test/java/de/ozgcloud/xta/client/xdomea/reader/ZipFileEntryReaderTest.java index 37d1a0346e915cdb7f7ac542cafcbf1c74029470..038e663db92ccaad468fd31429ca5357933b6908 100644 --- a/src/test/java/de/ozgcloud/xta/client/xdomea/reader/ZipFileEntryReaderTest.java +++ b/src/test/java/de/ozgcloud/xta/client/xdomea/reader/ZipFileEntryReaderTest.java @@ -16,7 +16,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import de.ozgcloud.common.errorhandling.TechnicalException; +import de.ozgcloud.xta.client.exception.XtaClientRuntimeException; class ZipFileEntryReaderTest { private static final DataHandler ZIP_FILE = EXAMPLE_ABGABE_MESSAGE.messageFile().content(); @@ -51,14 +51,14 @@ class ZipFileEntryReaderTest { @Test void shouldThrowExceptionIfEntryIsMissing() { assertThatThrownBy(() -> reader.mapEntryData(ZIP_FILE, "?", inputStreamConsumer)) - .isInstanceOf(TechnicalException.class); + .isInstanceOf(XtaClientRuntimeException.class); } @DisplayName("should throw exception on bad zip file") @Test void shouldThrowExceptionOnBadZipFile() { assertThatThrownBy(() -> reader.mapEntryData(BAD_ZIP_FILE, ENTRY_NAME, inputStreamConsumer)) - .isInstanceOf(TechnicalException.class); + .isInstanceOf(XtaClientRuntimeException.class); } } @@ -83,7 +83,7 @@ class ZipFileEntryReaderTest { @Test void shouldThrowExceptionOnBadZipFile() { assertThatThrownBy(() -> reader.getEntryNames(createMissingFileDataHandler())) - .isInstanceOf(TechnicalException.class); + .isInstanceOf(XtaClientRuntimeException.class); } private static DataHandler createMissingFileDataHandler() {