Skip to content

Commit

Permalink
add test demonstrating that FlyingSaucer can be used in parallel
Browse files Browse the repository at this point in the history
NB! ITextRenderer and SharedContext should not be shared between threads. They are NOT thread-safe.
  • Loading branch information
asolntsev committed Oct 2, 2023
1 parent 2232f46 commit 7303a70
Show file tree
Hide file tree
Showing 4 changed files with 657 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ public Map<String, String> parseCatalog(String catalogURI) {
URL url = requireNonNull(FSCatalog.class.getClassLoader().getResource(catalogURI),
() -> "Catalog not found in classpath: " + catalogURI);
return parseCatalog(url);
} catch (Exception ex) {
} catch (IOException ex) {
XRLog.xmlEntities(Level.WARNING, "Could not open XML catalog from URI '" + catalogURI + "'", ex);
return new HashMap<>();
throw new IllegalStateException("Cannot find " + catalogURI + " in classpath", ex);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public class FSEntityResolver implements EntityResolver2 {
/**
* Singleton instance, use {@link #instance()} to retrieve.
*/
private static FSEntityResolver instance;
private static final FSEntityResolver instance = new FSEntityResolver();

private final Map<String, String> entities = new HashMap<>();

Expand Down Expand Up @@ -150,10 +150,7 @@ private static InputSource newEmptySource() {
*
* @return An instance of .
*/
public static synchronized FSEntityResolver instance() {
if (instance == null) {
instance = new FSEntityResolver();
}
public static FSEntityResolver instance() {
return instance;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.xhtmlrenderer.pdf;

import com.codeborne.pdftest.PDF;
import com.lowagie.text.DocumentException;
import junit.framework.TestCase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xhtmlrenderer.resource.FSEntityResolver;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;

import static com.codeborne.pdftest.assertj.Assertions.assertThat;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.Executors.newFixedThreadPool;
import static java.util.concurrent.Executors.newScheduledThreadPool;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;

public class ConcurrentPdfGenerationTest extends TestCase {
private static final Logger log = LoggerFactory.getLogger(ConcurrentPdfGenerationTest.class);

public void testSamplePdf() {
byte[] pdf = generatePdf("sample.html");
verifyPdf(pdf);
}

public void testConcurrentPdfGeneration() throws InterruptedException {
ScheduledExecutorService timer = newScheduledThreadPool(1);
timer.scheduleWithFixedDelay(System::gc, 0, 15, MILLISECONDS);

ExecutorService pool = newFixedThreadPool(20);
for (int j = 0; j < 2000; j++) {
final int i = j;
pool.submit(() -> {
try {
byte[] pdf = generatePdf("sample.html");
verifyPdf(pdf);
log.info("Check #{} ok", i);
}
catch (Throwable e) {
log.error("Check #{} failed: ", i, e);
}
});
}

pool.shutdown();
assert pool.awaitTermination(250, SECONDS) : "Timeout!";
}

private void verifyPdf(byte[] pdfBytes) {
PDF pdf = new PDF(pdfBytes);
assertThat(pdf).containsText("Bill To:", "John doe", "[email protected]");
assertThat(pdf).containsText("Invoice #:", "INV-666");
assertThat(pdf).containsText("Invoice Date:", "Sep 27, 2023");
}

private byte[] generatePdf(String htmlPath) {
ITextRenderer renderer = new ITextRenderer();
renderer.getSharedContext().setMedia("pdf");
renderer.getSharedContext().setInteractive(false);
renderer.getSharedContext().getTextRenderer().setSmoothingThreshold(0);

URL htmlUrl = requireNonNull(Thread.currentThread().getContextClassLoader().getResource(htmlPath), () -> "Test resource not found: " + htmlPath);

try {
DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
builder.setEntityResolver(FSEntityResolver.instance());

Document doc = builder.parse(htmlUrl.openStream());

renderer.setDocument(doc, htmlUrl.toString());
renderer.layout();

ByteArrayOutputStream bos = new ByteArrayOutputStream();
renderer.createPDF(bos);
return bos.toByteArray();
}
catch (DocumentException | IOException | SAXException | ParserConfigurationException e) {
throw new IllegalArgumentException(e);
}
}

}
Loading

0 comments on commit 7303a70

Please sign in to comment.