Skip to content

Commit

Permalink
guide: Micronaut PDF OpenPDF (#1406)
Browse files Browse the repository at this point in the history
* guide: Micronaut PDF OpenPDF

* Whitespace license and unused imports

* use writabe instead

---------

Co-authored-by: Tim Yates <[email protected]>
  • Loading branch information
sdelamo and timyates authored Jan 19, 2024
1 parent 4f2f69d commit 6f848a2
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 0 deletions.
12 changes: 12 additions & 0 deletions buildSrc/src/main/java/io/micronaut/guides/feature/OpenPdf.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.micronaut.guides.feature;

import jakarta.inject.Singleton;

@Singleton
public class OpenPdf extends AbstractFeature {

public OpenPdf() {
super("openpdf");
}
}

5 changes: 5 additions & 0 deletions buildSrc/src/main/resources/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
</repositories>
<dependencies>
<dependency>
<groupId>com.github.librepdf</groupId>
<artifactId>openpdf</artifactId>
<version>1.3.35</version>
</dependency>
<dependency>
<groupId>org.webjars.npm</groupId>
<artifactId>hotwired__stimulus</artifactId>
<version>3.2.1</version>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.micronaut;

import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.io.Writable;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import com.lowagie.text.Document;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;

@Controller("/pdf") // <1>
class PDFController {

@ExecuteOn(TaskExecutors.BLOCKING) // <2>
@Get("/download") // <3>
HttpResponse<Writable> download() throws IOException { // <4>
return download("example.pdf");
}

private HttpResponse<Writable> download(String filename) throws IOException {
return HttpResponse.ok(pdfWritable())
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_PDF);
}

private Writable pdfWritable() {
return new Writable() {
public void writeTo(OutputStream outputStream, @Nullable Charset charset) throws IOException {
generatePDF(outputStream);
}

public void writeTo(Writer out) {
}
};
}

private void generatePDF(OutputStream outputStream) {
try (Document document = new Document()) {
PdfWriter.getInstance(document, outputStream);
document.open();
document.newPage();
document.add(new Paragraph("Hello World"));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2017-2024 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package example.micronaut;

import com.lowagie.text.pdf.PdfReader;
import com.lowagie.text.pdf.parser.PdfTextExtractor;
import io.micronaut.http.HttpHeaders;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import java.io.IOException;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest // <1>
class PDFControllerTest {

@Test
void testDownload(@Client("/") HttpClient httpClient) throws IOException { // <2>
BlockingHttpClient client = httpClient.toBlocking();
HttpResponse<byte[]> pdfResponse = assertDoesNotThrow(() -> client.exchange("/pdf/download", byte[].class));
assertEquals(HttpStatus.OK, pdfResponse.status());
assertEquals(MediaType.APPLICATION_PDF, pdfResponse.getHeaders().get(HttpHeaders.CONTENT_TYPE));
assertEquals("attachment; filename=example.pdf", pdfResponse.getHeaders().get(HttpHeaders.CONTENT_DISPOSITION));
byte[] pdfBytes = pdfResponse.body();
String firstPageText = textAtPage(pdfBytes, 1);
assertEquals("Hello World", firstPageText);
}

private static String textAtPage(PdfReader pdfReader, int pageNumber) throws IOException {
PdfTextExtractor pdfTextExtractor = new PdfTextExtractor(pdfReader);
return pdfTextExtractor.getTextFromPage(pageNumber);
}

private static String textAtPage(byte[] pdfBytes, int pageNumber) throws IOException {
try (PdfReader pdfReader = new PdfReader(pdfBytes)) {
return textAtPage(pdfReader, pageNumber);
}
}
}
15 changes: 15 additions & 0 deletions guides/micronaut-openpdf/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "Download a PDF with OpenPDF",
"intro": "Learn how to generate a PDF in a Micronaut Controller with OpenPDF.",
"authors": ["Sergio del Amo"],
"tags": ["openpdf"],
"categories": ["Beyond the Basics"],
"publicationDate": "2024-01-03",
"languages": ["java"],
"apps": [
{
"name": "default",
"invisibleFeatures": ["openpdf"]
}
]
}
52 changes: 52 additions & 0 deletions guides/micronaut-openpdf/micronaut-openpdf.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
common:header.adoc[]

common:requirements.adoc[]

common:completesolution.adoc[]

common:create-app-features.adoc[]

== OpenPDF

To generate PDFs, we use https://github.com/LibrePDF/OpenPDF[OpenPDF].

____
OpenPDF is a Java library for creating and editing PDF files with a LGPL and MPL open source license. OpenPDF is the LGPL/MPL open source successor of iText, and is based on some forks of iText 4 svn tag. We welcome contributions from other developers. Please feel free to submit pull-requests and bugreports to this GitHub repository.
____

=== OpenPDF Dependency

To use OpenPDF, add the following dependency:

dependency:openpdf[groupId=com.github.librepdf,scope=implementation,version=@openpdfVersion@]

== Controller

Create a controller that generates and downloads a PDF.

source:PDFController[]]
callout:controller[number=1,arg0=/pdf]
callout:executes-on[2]
callout:get[number=3,arg0=download,arg1=/download]
callout:writable[4]

== Test

Create a test that verifies the controller's download matches the expected text.

test:PDFControllerTest[]

callout:micronaut-test[1]
callout:http-client[2]

common:testApp.adoc[]

common:runapp.adoc[]

Visit http://localhost:8080/pdf/download and the browser downloads a PDF.

common:next.adoc[]

Learn more about https://github.com/LibrePDF/OpenPDF[OpenPDF].

common:helpWithMicronaut.adoc[]
1 change: 1 addition & 0 deletions src/docs/common/callouts/callout-writable.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
When returning a https://docs.micronaut.io/latest/api/io/micronaut/core/io/Writable.html[Writable], the blocking I/O operation is shifted to the I/O thread pool so the Netty event loop is not blocked.

0 comments on commit 6f848a2

Please sign in to comment.