Skip to content

Commit c9d5df0

Browse files
authored
Merge pull request valtimo-platform#591 from Baseflow/preview_improve_documentation
Improvements on the documenten-api-preview plugin.
2 parents deb8025 + e5d457c commit c9d5df0

19 files changed

Lines changed: 322 additions & 45 deletions

File tree

backend/app/gzac/docker-compose.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ services:
105105
container_name: gotenberg-pdf-conversion-api
106106
ports:
107107
- "3000:3000"
108+
environment:
109+
API_TIMEOUT: "240s"
108110

109111
# ZGW
110112

backend/app/gzac/src/main/resources/application.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,11 @@ valtimo:
228228
- image/jpeg
229229
- image/png
230230
- application/msword
231+
- application/vnd.ms-excel
232+
- application/vnd.ms-powerpoint
231233
- application/vnd.openxmlformats-officedocument.wordprocessingml.document
234+
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
235+
- application/vnd.openxmlformats-officedocument.presentationml.presentation
232236

233237
outbox:
234238
enabled: true

backend/zgw/documenten-api-preview/src/main/kotlin/com/ritense/documentenapipreview/DocumentenApiPreviewPlugin.kt

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.ritense.documentenapi.DocumentenApiPlugin
2121
import com.ritense.documentenapi.client.DocumentInformatieObject
2222
import com.ritense.documentenapipreview.DocumentenApiPreviewPlugin.Companion.PLUGIN_KEY
2323
import com.ritense.documentenapipreview.client.PdfConversionClient
24+
import com.ritense.documentenapipreview.domain.PdfArchiveMethod
2425
import com.ritense.documentenapipreview.domain.PdfFile
2526
import com.ritense.plugin.annotation.Plugin
2627
import com.ritense.plugin.annotation.PluginProperty
@@ -46,6 +47,12 @@ class DocumentenApiPreviewPlugin(
4647
@PluginProperty(key = DOCUMENTEN_API_CONFIGURATION_ID, secret = false)
4748
lateinit var documentenApiConfigurationId: String
4849

50+
@PluginProperty(key = PDF_ARCHIVE_METHOD, secret = false, required = false)
51+
var pdfArchiveMethod: PdfArchiveMethod = PdfArchiveMethod.NONE
52+
53+
@PluginProperty(key = PDF_UNIVERSAL_ACCESSIBILITY, secret = false, required = false)
54+
var pdfUniversalAccessibility: Boolean = false
55+
4956
fun generatePreview(caseDocumentId: UUID, documentId: String): PdfFile {
5057
val documentenApiPlugin = getDocumentenApiPlugin()
5158
val documentStream = documentenApiPlugin.downloadInformatieObject(caseDocumentId, documentId)
@@ -58,7 +65,12 @@ class DocumentenApiPreviewPlugin(
5865
return PdfFile(documentInformatieObject.bestandsnaam!!, documentStream)
5966
}
6067

61-
val pdfStream = pdfConversionClient.convertDocument(pdfConversionUrl, documentStream, documentInformatieObject.bestandsnaam)
68+
val pdfStream = pdfConversionClient.convertDocument(
69+
pdfConversionUrl,
70+
documentStream,
71+
documentInformatieObject.bestandsnaam,
72+
pdfArchiveMethod,
73+
pdfUniversalAccessibility)
6274

6375
return PdfFile(createFilename(documentInformatieObject), pdfStream)
6476
}
@@ -78,6 +90,9 @@ class DocumentenApiPreviewPlugin(
7890
const val PLUGIN_KEY = "documentenapipreview"
7991
const val PDF_CONVERSION_URL_PROPERTY = "pdfConversionUrl"
8092
const val DOCUMENTEN_API_CONFIGURATION_ID = "documentenApiConfigurationId"
93+
const val PDF_ARCHIVE_METHOD = "pdfArchiveMethod"
94+
const val PDF_UNIVERSAL_ACCESSIBILITY = "pdfUniversalAccessibility"
95+
8196

8297
fun findConfigurationByDocumentenApiConfiguration(documentenApiConfigurationId: String) = { properties: JsonNode ->
8398
documentenApiConfigurationId == properties[DOCUMENTEN_API_CONFIGURATION_ID].textValue()

backend/zgw/documenten-api-preview/src/main/kotlin/com/ritense/documentenapipreview/client/PdfConversionClient.kt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.ritense.documentenapipreview.client
1818

19+
import com.ritense.documentenapipreview.domain.PdfArchiveMethod
1920
import com.ritense.valtimo.contract.annotation.SkipComponentScan
2021
import com.ritense.zgw.ClientTools
2122
import org.springframework.core.io.InputStreamResource
@@ -37,12 +38,16 @@ class PdfConversionClient(
3738
baseUrl: URI,
3839
document: InputStream,
3940
fileName: String? = null,
41+
pdfArchiveMethod: PdfArchiveMethod = PdfArchiveMethod.NONE,
42+
pdfUniversalAccessibility: Boolean = false,
4043
): InputStream {
44+
45+
4146
val bodyBuilder = MultipartBodyBuilder().apply {
4247
part("files", InputStreamResource(document)).filename(fileName ?: "file_name_unknown")
4348
part("exportFormFields", "false")
44-
part("pdfa", "PDF/A-1b")
45-
part("pdfua", "true")
49+
convertPdfArchiveMethodToGotenbergValue(pdfArchiveMethod)?.let { part("pdfa", it) }
50+
part("pdfua", pdfUniversalAccessibility.toString())
4651
}
4752

4853
val result = restClient()
@@ -59,6 +64,15 @@ class PdfConversionClient(
5964
return result.inputStream
6065
}
6166

67+
private fun convertPdfArchiveMethodToGotenbergValue(pdfArchiveMethod: PdfArchiveMethod): String? {
68+
return when (pdfArchiveMethod) {
69+
PdfArchiveMethod.NONE -> null
70+
PdfArchiveMethod.PDFA1B -> "PDF/A-1b"
71+
PdfArchiveMethod.PDFA2B -> "PDF/A-2b"
72+
PdfArchiveMethod.PDFA3B -> "PDF/A-3b"
73+
}
74+
}
75+
6276
private fun restClient(): RestClient {
6377
return restClientBuilder
6478
.clone()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2015-2024 Ritense BV, the Netherlands.
3+
*
4+
* Licensed under EUPL, Version 1.2 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" basis,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.ritense.documentenapipreview.domain
18+
19+
import com.fasterxml.jackson.annotation.JsonProperty
20+
21+
enum class PdfArchiveMethod {
22+
@JsonProperty("none")
23+
NONE,
24+
25+
@JsonProperty("PDF/A-1b")
26+
PDFA1B,
27+
28+
@JsonProperty("PDF/A-2b")
29+
PDFA2B,
30+
31+
@JsonProperty("PDF/A-3b")
32+
PDFA3B,
33+
}

backend/zgw/documenten-api-preview/src/test/kotlin/com/ritense/documentenapipreview/DocumentenApiPreviewPluginTest.kt

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.ritense.documentenapipreview
1919
import com.ritense.documentenapi.DocumentenApiPlugin
2020
import com.ritense.documentenapi.client.DocumentInformatieObject
2121
import com.ritense.documentenapipreview.client.PdfConversionClient
22+
import com.ritense.documentenapipreview.domain.PdfArchiveMethod
2223
import com.ritense.plugin.service.PluginService
2324
import com.ritense.zgw.Rsin
2425
import org.junit.jupiter.api.BeforeEach
@@ -35,6 +36,7 @@ import java.time.OffsetDateTime
3536
import java.util.UUID
3637

3738
class DocumentenApiPreviewPluginTest {
39+
private val documentenApiConfigurationId: String = "mock_documenten_api_preview_configuration_id"
3840
private lateinit var documentenApiPreviewPlugin: DocumentenApiPreviewPlugin
3941
private lateinit var documentenApiPlugin: DocumentenApiPlugin
4042
private lateinit var pdfConversionClient: PdfConversionClient
@@ -47,43 +49,71 @@ class DocumentenApiPreviewPluginTest {
4749
pdfConversionClient = mock<PdfConversionClient>()
4850
pluginService = mock<PluginService>()
4951

50-
documentenApiPreviewPlugin = DocumentenApiPreviewPlugin(pdfConversionClient, pluginService)
51-
documentenApiPreviewPlugin.documentenApiConfigurationId = "mock_documenten_api_configuration_id"
52-
documentenApiPreviewPlugin.pdfConversionUrl = URI("http://mock.url")
53-
5452
mockDocumentStream = "TEST_DOCUMENT".byteInputStream()
5553

56-
whenever(pluginService.createInstance<DocumentenApiPlugin>(documentenApiPreviewPlugin.documentenApiConfigurationId))
54+
whenever(pluginService.createInstance<DocumentenApiPlugin>(documentenApiConfigurationId))
5755
.thenReturn(documentenApiPlugin)
5856
whenever(documentenApiPlugin.downloadInformatieObject(MOCK_CASE_DOCUMENT_ID, MOCK_DOCUMENT_ID))
5957
.thenReturn(mockDocumentStream)
6058
whenever(documentenApiPlugin.getInformatieObject(MOCK_DOCUMENT_ID, MOCK_CASE_DOCUMENT_ID))
6159
.thenReturn(MOCK_DOCUMENT_INFORMATIE_OBJECT)
62-
whenever(pdfConversionClient.convertDocument(any(), any(), any())).thenReturn(mockDocumentStream)
60+
whenever(pdfConversionClient.convertDocument(any(), any(), any(), any(), any())).thenReturn(mockDocumentStream)
6361
}
6462

6563
@Test
6664
fun `should call download on DocumentenApiPlugin`() {
65+
documentenApiPreviewPlugin = DocumentenApiPreviewPlugin(pdfConversionClient, pluginService)
66+
documentenApiPreviewPlugin.documentenApiConfigurationId = documentenApiConfigurationId
67+
documentenApiPreviewPlugin.pdfConversionUrl = URI("http://mock.url")
68+
6769
documentenApiPreviewPlugin.generatePreview(MOCK_CASE_DOCUMENT_ID, MOCK_DOCUMENT_ID)
6870

6971
verify(documentenApiPlugin).downloadInformatieObject(MOCK_CASE_DOCUMENT_ID, MOCK_DOCUMENT_ID)
7072
}
7173

7274
@Test
7375
fun `should call getInformatieObject on DocumentenApiPlugin`() {
76+
documentenApiPreviewPlugin = DocumentenApiPreviewPlugin(pdfConversionClient, pluginService)
77+
documentenApiPreviewPlugin.documentenApiConfigurationId = documentenApiConfigurationId
78+
documentenApiPreviewPlugin.pdfConversionUrl = URI("http://mock.url")
79+
7480
documentenApiPreviewPlugin.generatePreview(MOCK_CASE_DOCUMENT_ID, MOCK_DOCUMENT_ID)
7581

7682
verify(documentenApiPlugin).getInformatieObject(MOCK_DOCUMENT_ID, MOCK_CASE_DOCUMENT_ID)
7783
}
7884

7985
@Test
80-
fun `should call generatePreview on PdfConversionClient`() {
86+
fun `should call generatePreview on PdfConversionClient with default parameters`() {
87+
documentenApiPreviewPlugin = DocumentenApiPreviewPlugin(pdfConversionClient, pluginService)
88+
documentenApiPreviewPlugin.documentenApiConfigurationId = documentenApiConfigurationId
89+
documentenApiPreviewPlugin.pdfConversionUrl = URI("http://mock.url")
90+
91+
documentenApiPreviewPlugin.generatePreview(MOCK_CASE_DOCUMENT_ID, MOCK_DOCUMENT_ID)
92+
93+
verify(pdfConversionClient).convertDocument(
94+
documentenApiPreviewPlugin.pdfConversionUrl,
95+
mockDocumentStream,
96+
MOCK_DOCUMENT_INFORMATIE_OBJECT.bestandsnaam,
97+
PdfArchiveMethod.NONE,
98+
false)
99+
}
100+
101+
@Test
102+
fun `should call generatePreview on PdfConversionClient with custom parameters`() {
103+
documentenApiPreviewPlugin = DocumentenApiPreviewPlugin(pdfConversionClient, pluginService)
104+
documentenApiPreviewPlugin.documentenApiConfigurationId = documentenApiConfigurationId
105+
documentenApiPreviewPlugin.pdfConversionUrl = URI("http://mock.url")
106+
documentenApiPreviewPlugin.pdfArchiveMethod = PdfArchiveMethod.PDFA2B
107+
documentenApiPreviewPlugin.pdfUniversalAccessibility = true
108+
81109
documentenApiPreviewPlugin.generatePreview(MOCK_CASE_DOCUMENT_ID, MOCK_DOCUMENT_ID)
82110

83111
verify(pdfConversionClient).convertDocument(
84112
documentenApiPreviewPlugin.pdfConversionUrl,
85113
mockDocumentStream,
86-
MOCK_DOCUMENT_INFORMATIE_OBJECT.bestandsnaam)
114+
MOCK_DOCUMENT_INFORMATIE_OBJECT.bestandsnaam,
115+
documentenApiPreviewPlugin.pdfArchiveMethod,
116+
documentenApiPreviewPlugin.pdfUniversalAccessibility)
87117
}
88118

89119
companion object {

backend/zgw/documenten-api-preview/src/test/kotlin/com/ritense/documentenapipreview/client/PdfConversionClientIT.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.ritense.documentenapipreview.client
1919
import com.fasterxml.jackson.databind.ObjectMapper
2020
import com.ritense.documentenapipreview.BaseIntegrationTest
2121
import com.ritense.documentenapipreview.DocumentenApiPreviewPlugin
22+
import com.ritense.documentenapipreview.domain.PdfArchiveMethod
2223
import okhttp3.mockwebserver.Dispatcher
2324
import okhttp3.mockwebserver.MockResponse
2425

@@ -67,7 +68,9 @@ internal class PdfConversionClientIT @Autowired constructor(
6768
val stream = pdfConversionClient.convertDocument(
6869
documentenApiPreviewPlugin.pdfConversionUrl,
6970
"test_document".byteInputStream(),
70-
"dummy_file.txt"
71+
"dummy_file.txt",
72+
PdfArchiveMethod.PDFA2B,
73+
true
7174
)
7275

7376
assertNotNull(stream)

backend/zgw/documenten-api-preview/src/test/kotlin/com/ritense/documentenapipreview/client/PdfConversionClientTest.kt

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.ritense.documentenapipreview.client
1818

19+
import com.ritense.documentenapipreview.domain.PdfArchiveMethod
1920
import okhttp3.mockwebserver.MockResponse
2021
import okhttp3.mockwebserver.MockWebServer
2122
import okhttp3.mockwebserver.RecordedRequest
@@ -24,7 +25,6 @@ import org.junit.jupiter.api.AfterAll
2425
import org.junit.jupiter.api.BeforeAll
2526
import org.junit.jupiter.api.Test
2627
import org.junit.jupiter.api.TestInstance
27-
import org.junit.jupiter.api.assertNotNull
2828
import org.springframework.http.MediaType
2929
import org.springframework.web.client.RestClient
3030
import java.io.ByteArrayInputStream
@@ -33,6 +33,8 @@ import java.util.HashMap
3333
import java.util.concurrent.TimeUnit
3434
import kotlin.test.assertEquals
3535
import kotlin.test.assertTrue
36+
import kotlin.test.assertNull
37+
import kotlin.test.assertNotNull
3638

3739

3840
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@@ -71,7 +73,7 @@ internal class PdfConversionClientTest {
7173
}
7274

7375
@Test
74-
fun `should send request with correct form fields`() {
76+
fun `should send request with default conversion parameters`() {
7577
val restClientBuilder = RestClient.builder()
7678
val client = PdfConversionClient(restClientBuilder)
7779

@@ -82,7 +84,37 @@ internal class PdfConversionClientTest {
8284
client.convertDocument(
8385
mockPdfConversionApi.url("/").toUri(),
8486
"TEST_DOCUMENT".byteInputStream(),
85-
"mock_document.txt")
87+
"mock_document.txt"
88+
)
89+
90+
val recordedRequest = mockPdfConversionApi.takeRequest(5, TimeUnit.SECONDS)
91+
assertNotNull(recordedRequest)
92+
93+
val formFields = parseMultipartFormData(recordedRequest)
94+
95+
assertTrue(recordedRequest.getHeader("Content-Type")?.startsWith("multipart/form-data") ?: false )
96+
assertEquals("TEST_DOCUMENT", formFields["files"])
97+
assertEquals("false",formFields["exportFormFields"])
98+
assertNull(formFields["pdfa"])
99+
assertEquals("false",formFields["pdfua"])
100+
}
101+
102+
@Test
103+
fun `should send request with PDF A-2b and PDF UA set to true`() {
104+
val restClientBuilder = RestClient.builder()
105+
val client = PdfConversionClient(restClientBuilder)
106+
107+
val responseBody = "TEST_PDF_CONTENT"
108+
109+
mockPdfConversionApi.enqueue(mockResponse(responseBody))
110+
111+
client.convertDocument(
112+
mockPdfConversionApi.url("/").toUri(),
113+
"TEST_DOCUMENT".byteInputStream(),
114+
"mock_document.txt",
115+
PdfArchiveMethod.PDFA2B,
116+
true
117+
)
86118

87119
val recordedRequest = mockPdfConversionApi.takeRequest(5, TimeUnit.SECONDS)
88120
assertNotNull(recordedRequest)
@@ -92,10 +124,39 @@ internal class PdfConversionClientTest {
92124
assertTrue(recordedRequest.getHeader("Content-Type")?.startsWith("multipart/form-data") ?: false )
93125
assertEquals("TEST_DOCUMENT", formFields["files"])
94126
assertEquals("false",formFields["exportFormFields"])
95-
assertEquals("PDF/A-1b", formFields["pdfa"])
127+
assertEquals("PDF/A-2b", formFields["pdfa"])
96128
assertEquals("true", formFields["pdfua"] )
97129
}
98130

131+
@Test
132+
fun `should send request with PDF A-3b and PDF UA set to false`() {
133+
val restClientBuilder = RestClient.builder()
134+
val client = PdfConversionClient(restClientBuilder)
135+
136+
val responseBody = "TEST_PDF_CONTENT"
137+
138+
mockPdfConversionApi.enqueue(mockResponse(responseBody))
139+
140+
client.convertDocument(
141+
mockPdfConversionApi.url("/").toUri(),
142+
"TEST_DOCUMENT".byteInputStream(),
143+
"mock_document.txt",
144+
PdfArchiveMethod.PDFA3B,
145+
false
146+
)
147+
148+
val recordedRequest = mockPdfConversionApi.takeRequest(5, TimeUnit.SECONDS)
149+
assertNotNull(recordedRequest)
150+
151+
val formFields = parseMultipartFormData(recordedRequest)
152+
153+
assertTrue(recordedRequest.getHeader("Content-Type")?.startsWith("multipart/form-data") ?: false )
154+
assertEquals("TEST_DOCUMENT", formFields["files"])
155+
assertEquals("false",formFields["exportFormFields"])
156+
assertEquals("PDF/A-3b", formFields["pdfa"])
157+
assertEquals("false", formFields["pdfua"] )
158+
}
159+
99160
private fun mockResponse(body: String): MockResponse {
100161
return MockResponse()
101162
.addHeader("Content-Type", "application/pdf")

documentation/features/plugins/configure-documenten-api-preview-plugin.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ the original documents to PDF format is done using the open-source project [Gote
1616
a Docker-based API specifically designed to convert documents to PDF. This means the "Documenten API Preview plugin"
1717
requires some configuration. A general description on how to configure plugins can be found in the [plugin configuration guide](./configure-plugin.md).
1818

19+
> IMPORTANT:
20+
>
21+
> The [Gotenberg][1] PDF conversion API is available as a docker image and should be installed separately (see [installation guide](https://gotenberg.dev/docs/getting-started/installation)).
22+
1923
To configure this plugin the following properties have to be entered:
2024

2125
* **Configuration ID (`configurationId`).** The plugin will be saved under this ID. The ID must be in the format of a UUID.

0 commit comments

Comments
 (0)