Skip to content

Commit b150d87

Browse files
committed
Update to v1.3 with the following:
- Added support for Azure Function configuration capability to enable Accessibility since Apache FOP `<accessibility>` xml config element is not working as of v2.6. - Added an XslFO markup sample to test/demonstrate Accessibility in `resources/samples/WorkinWithAccessibilitySample.fo`. - Updated KeepinItWarm.fo to run correctly when Accessibility is enabled. - Added in-memory caching of Java embedded resources that are resolved (e.g. Fonts) for performance. - Code cleanup.
1 parent 0a218fa commit b150d87

File tree

15 files changed

+262
-140
lines changed

15 files changed

+262
-140
lines changed

README.md

Lines changed: 35 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,23 @@ then I do love-me-some-coffee!*
1313
<img src="https://cdn.buymeacoffee.com/buttons/default-orange.png" alt="Buy Me A Coffee" height="41" width="174">
1414
</a>
1515

16-
## Updates
17-
**Updated the project to v1.2 with the following:**
16+
## Updates / Change Log
17+
##### Updated the project to v1.3 with the following:
18+
- Added support for Azure Function configuration capability to enable Accessibility since Apache FOP `<accessibility>` xml config element is not working as of v2.6.
19+
- Added an XslFO markup sample to test/demonstrate Accessibility in `resources/samples/WorkinWithAccessibilitySample.fo`.
20+
- Updated KeepinItWarm.fo to run correctly when Accessibility is enabled.
21+
- Added in-memory caching of Java embedded resources that are resolved (e.g. Fonts) for performance.
22+
- Code cleanup.
23+
24+
##### Updated the project to v1.2 with the following:
1825
- Added support for Custom Font integration as Resource Files in the project and deployed with the JAR!
1926
- This enables adding fonts easily by simply dropping them in the `resources/fonts` folder, and then registering them via configuration in the `apache-fop-config.xml` according to [Apache FOP Font Documentation](https://xmlgraphics.apache.org/fop/2.6/fonts.html#register).
20-
- Added a a couple sample (free) custom fonts and sample markup `resources/samples/WorkinWithFontsSampe.fo` in the project.
27+
- Added a a couple sample (free) custom fonts and sample markup `resources/samples/WorkinWithFontsSample.fo` in the project.
2128
- Fixed bug in the render Event Log debug details returned in the Http Header whereby Apache FOP may send Unicode Characters but only ASCII are allowed; Unicode are now safely escaped into their respective Hex Codes so that the message is readable.
2229
- Fixed issue in Maven build to enforce the clean stage so the artifact always contains the latest changes (e.g. especially physical resource file changes) when debugging.
2330
- Some miscellaneous code cleanup.
2431

25-
Updated the project to v1.1 as it now incorporates:
32+
##### Updated the project to v1.1 as it now incorporates:
2633
- Upgraded the project to use Java v11 now as the latest long term supported (LTS) version for Azure Functions (aka Zulu Java v11)
2734
- _Previously was Java 8 (v1.8) (aka Zulu Java v8)._
2835
- Bumping the versions of all dependencies to the latest stable versions
@@ -69,7 +76,7 @@ I ramble on about that over here in:
6976

7077
Suffice it to say that markup based solutions have alot of value, and Xsl-FO is still one of the best ways to maintain strong software development practices by rendering PDF outputs (as a presentation output) from separated content/data + template. And Xsl-FO offers features that some approaches just can't do (looking at you *Crystal Reports*).
7178

72-
There has been a fully managed .Net C# port of [Apache FOP](https://xmlgraphics.apache.org/fop/) (FO.Net) based on a pre-v1.0 version (*is my guesstimate*); it's old & unsupported, but still fairly functional, and I've used it very successfully on several projects. But Apache FOP is now on [v2.6 as of Jan 2021!](https://xmlgraphics.apache.org/fop/2.6/changes_2.6.html) with annual/bi-annual support updates still being released.
79+
There has been a fully managed .Net C# port of [Apache FOP](https://xmlgraphics.apache.org/fop/) (FO.Net) based on a pre-v1.0 version (*is my guesstimate*); it's old & unsupported, but still fairly functional, and I've used it very successfully on several projects. But Apache FOP is now on [v2.5 as of May 2020!](https://xmlgraphics.apache.org/fop/2.5/changes_2.5.html) with annual/bi-annual support updates still being released.
7380

7481
So my goal has been, for a while, to take advantage of the many great innovations in the past several years to provide an interoperable integration between Java Apache FOP and .Net, without resorting to [something that makes my eyes cross (ugg).](http://codemesh.com/products/juggernet/).
7582

@@ -126,52 +133,21 @@ files by simply placing them in the `src/main/resources/fonts` folder. So you c
126133

127134
Once there they can be resolved at runtime by the application even after being deployed to Azure Functions; because they will be embedded resources with the JAR file. ApacheFOP.Serverless has a custom `ResourceResolver` implementation that can then locate these via relative path is used when registering the fonts via configuration in the `apache-fop-config.xml` according to [Apache FOP Font Documentation](https://xmlgraphics.apache.org/fop/2.6/fonts.html#register).
128135

129-
##### Register them with Apache FOP Configuration:
130-
Now we need to register the fonts we've placed into our resources. We do this by editing the `resources/apache-fop-config.xml` according to [Apache FOP Font Documentation](https://xmlgraphics.apache.org/fop/2.6/fonts.html#register). The project includes a couple sample fonts as well as a sample Xsl FO markup file that will render using these new custom fonts located at `resources/samples/WorkinWithFontsSample.fo`.
131-
132-
The font registration configuration looks like this:
133-
```
134-
<fop version="1.0">
135-
. . .
136-
<renderers>
137-
<renderer mime="application/pdf">
138-
. . .
139-
140-
<fonts>
141-
<!-- Automatically detect operating system installed fonts; More Details Here: https://xmlgraphics.apache.org/fop/2.6/fonts.html -->
142-
<auto-detect/>
143-
144-
<font embed-url="fonts/Minisystem.ttf" simulate-style="true">
145-
<font-triplet name="Minisystem" style="normal" weight="normal" />
146-
</font>
147-
<font embed-url="fonts/PixelWow.otf" simulate-style="true">
148-
<font-triplet name="PixelWow" style="normal" weight="normal" />
149-
</font>
150-
</fonts>
151-
. . .
152-
</renderer>
153-
</renderers>
154-
</fop>
155-
```
136+
### Enable Accessibility:
137+
Apache FOP Supports accessibility compliance in PDFs however, the `<accessibility>` xml configuration attribute noted in the documentation ([here](https://xmlgraphics.apache.org/fop/2.6/accessibility.html)) does not work as of v2.6.
156138

139+
Therefore ApacheFOP.Serverless provides an Azure Function configuration value to set this directly which can be enabled by setting the Azure Functions environment config value: `'AccessibilityEnabled' = 'true'`.
157140

158-
## Calling the Service from .Net (ApacheFOP.Serverless REST Client on Nuget):
159-
Below is a snippet to make a simplified/straightforward call, however _ApacheFOP.Serverless_ has a several other advanced features including compression options and debugging outputs. So to make things alot easier I've shared a very lightweight REST Client as a .Net Standard 2.0 library that can be used in any .Net project. It provides options to easily use all of the advanced features and handles debugging details automatically.
141+
## Calling the Service from .Net
160142

161-
To easily add support for _ApacheFOP.Serverless_ into your project just add the ready-to-go client libary **availalbe on Nuget here:** [**PdfTemplating.XslFO.Render.ApacheFOP.Serverless Client Library**](https://www.nuget.org/packages/PdfTemplating.XslFO.Render.ApacheFOP.Serverless/)
162-
163-
Also, I provide additional usage details in my article (mentioned below), but you can jump to the [_ApacheFOP.Serverless_ REST Client details here...](https://cajuncoding.com/2021-08-22-pdf-reporting-with-a-serverless-architecture/#ApacheFopServerlessClient)
164-
165-
### Simplified Snippet:
143+
### Snippet:
166144
Because I talked about follow-through up above, I'd be amiss if I didn't provide a sample implementation of calling this code from .Net.
167145

168-
Assuming the use of the *RESTSharp library* for REST api calls, and the Xsl-FO content is validated and parsed as an *XDocument* (Linq2Xml)... this sample should get you started on the .Net side as a client calleing the new PDF microservice.
169-
170-
*NOTE: Just use RESTSharp or Flurl and avoid [incorrectly implementing HttpClient (hint, it should be a singleton)](https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/)*
146+
Assuming the use of the great *RESTSharp library* for REST api calls, and the Xsl-FO content is validated and parsed as an *XDocument* (Linq2Xml)... this sample should get you started on the .Net side as a client calleing the new PDF microservice.
171147

172-
Snippet is a very simplified version taken from the [.Net Client implementation](https://github.com/cajuncoding/PdfTemplating.XslFO/blob/f6f22b09e110954f3ccdde2f53437b0ab1041ccb/PdfTemplating.XslFO.Render.ApacheFOP.Serverless/ApacheFOPServerlessPdfRenderService.cs#L50), in the PdfTemplating.XslFO demo project (mentioned above).
148+
*NOTE: Just use RESTSharp and avoid [incorrectly implementing HttpClient (hint, it should be a singleton)](https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/)*
173149

174-
_Note: the `AddRawTextBody()` method is a custom extension that handles some idiosynchrosies of adding raw text for the POST with RESTSharp.... I'll probably be migrating to use [Flurl](https://flurl.dev/) instead in the future._
150+
Snippet taken from the [implementation here](https://github.com/cajuncoding/PdfTemplating.XslFO/blob/feature/iniial_support_for_apache_fop_serverless_rendering/PdfTemplating.XslFO.Render.ApacheFOP.Serverless/XslFOPdfRenderService.cs), in my PdfTemplating project.
175151

176152
```csharp
177153
using RestSharp;
@@ -212,7 +188,7 @@ namespace PdfTemplating.XslFO.ApacheFOP.Serverless
212188
```
213189

214190
### .Net PdfTemplating (Full blown) Implementation:
215-
A full blown demo implementation of templating + ApacheFOP.Serverless, as well as ready-to-go .Net Clients (via Nuget package) can be found in my [Pdf Templating project here](https://github.com/cajuncoding/PdfTemplating.XslFO).
191+
A full blown implementation of templating + ApacheFOP.Serverless is in a branch of my [Pdf Templating project here](https://github.com/cajuncoding/PdfTemplating.XslFO/tree/feature/iniial_support_for_apache_fop_serverless_rendering).
216192

217193
It illustrates the use of both Xslt and/or Razor templates from ASP.Net MVC to render PDF Binary reports dynamically from queries to the [Open Movie Database API](http://www.omdbapi.com/). And it has now been enhanced to also illustrate the use of _ApacehFOP.Serverless_ microservice for rendering instead of the embedded legacy FO.Net implementation.
218194

@@ -227,7 +203,20 @@ With the running application provided in the project above, the following page u
227203
<img src="/pdf-templating-apache-fop-serverless-chrome-test.png" style="width:auto;height:auto;max-width:1200px;">
228204
</p>
229205

206+
230207
## Additional Background:
231-
For additional details check out my article [**_PDF Reporting with a Serverless Architecture_**](https://cajuncoding.com/2021-08-22-pdf-reporting-with-a-serverless-architecture/), where I provide a broader overview of the background, architecture, and additional details around the _ApacheFOP.Serverless_ project.
208+
For many-many years, I've implemented Pdf Reporting solutions with [templating approaches](https://github.com/cajuncoding/PdfTemplating.XslFO) for various clients (enterprises & small businesses) to help them automate their paper processes with dynamic generation of _printable media_ outputs such as: PDF files, invoices, shipping/packaging labels, newletters, etc.
209+
210+
And, for a long while now I've known that the current C# implementation FO.Net was limited by the fact that it was created circa 2008 and is now [an archived CodePlex project](https://archive.codeplex.com/?p=fonet).
211+
212+
At one client the technology stack was fully Java based, so the use of _Apache FOP_ was a no-brainer; [ApacheFOP](https://xmlgraphics.apache.org/fop/) is a supported, open-source, full implementation of an XSL-FO processor in Java, that has had regular updates/enhancements over the years.
213+
214+
The [FO.Net](https://archive.codeplex.com/?p=fonet) C# variant was ported from Apache FOP; likely from a pre-v1.0 version of ApacheFOP, but to be
215+
honest it has worked incredibly well, and reliably. As a fully managed C# solution, it ran in web projects as well a WinForms projects where viewing
216+
the rendered PDF live int the app real-time provided and wonderful user experience for a couple of projects.
217+
218+
But, as things have evolved the advent of cloud services has opened doors for accomplishing this in a much more powerful/scaleable/manageable way -- particularly Azure Functions and their excellent support for varios technology languages including: .Net, Java, NodeJS, etc.!
219+
220+
So I finally had the time to flush out the details, and share this project. I truly hope that it helps many others out!
232221

233222
Now Geaux Code!

apachefop-serverless-az-func/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>com.cajuncoding</groupId>
66
<artifactId>apachefop-serverless-az-func</artifactId>
7-
<version>1.2-SNAPSHOT</version>
7+
<version>1.3-SNAPSHOT</version>
88
<packaging>jar</packaging>
99

1010
<name>Azure Java Functions</name>

apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/KeepWarmFunction.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.cajuncoding.apachefop.serverless;
22

33
import com.cajuncoding.apachefop.serverless.apachefop.ApacheFopRenderer;
4+
import com.cajuncoding.apachefop.serverless.config.ApacheFopServerlessConfig;
45
import com.cajuncoding.apachefop.serverless.config.ApacheFopServerlessConstants;
56
import com.cajuncoding.apachefop.serverless.utils.ResourceUtils;
67
import com.cajuncoding.apachefop.serverless.utils.TextUtils;
@@ -33,15 +34,18 @@ public void run(
3334

3435
logger.info(MessageFormat.format(" - Executing at [{0}]", TextUtils.getCurrentW3cDateTime()));
3536

36-
var xslFoSource = ResourceUtils.LoadResourceAsString(ApacheFopServerlessConstants.KeepinItWarmXslFo);
37+
var xslFoSource = ResourceUtils.loadResourceAsString(ApacheFopServerlessConstants.KeepinItWarmXslFo);
3738
logger.info(MessageFormat.format(" - XSL-FO Keep Warm XslFo Script Loaded [Length={0}]", xslFoSource.length()));
3839

3940
//Now we process the XSL-FO source...
4041
logger.info("Executing Transformation with Apache FOP...");
4142

43+
//Read the Configuration from AzureFunctions (request, environment variables)
44+
var config = new ApacheFopServerlessConfig();
45+
4246
//Initialize the ApacheFopRenderer (potentially optimized with less logging.
4347
//NOTE: If used, the Logger must be the instance injected into the Azure Function!
44-
ApacheFopRenderer fopHelper = new ApacheFopRenderer(logger);
48+
ApacheFopRenderer fopHelper = new ApacheFopRenderer(config, logger);
4549

4650
//Execute the transformation of the XSL-FO source content to Binary PDF format...
4751
var pdfRenderResult = fopHelper.renderPdfResult(xslFoSource, false);

apachefop-serverless-az-func/src/main/java/com/cajuncoding/apachefop/serverless/apachefop/ApacheFopJavaResourcesFileResolver.java

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,35 @@
11
package com.cajuncoding.apachefop.serverless.apachefop;
22

33
import com.cajuncoding.apachefop.serverless.utils.ResourceUtils;
4+
import org.apache.commons.io.IOUtils;
45
import org.apache.fop.apps.io.ResourceResolverFactory;
56
import org.apache.xmlgraphics.io.Resource;
67
import org.apache.xmlgraphics.io.ResourceResolver;
78

9+
import java.io.ByteArrayInputStream;
810
import java.io.IOException;
911
import java.io.InputStream;
1012
import java.io.OutputStream;
1113
import java.net.URI;
1214
import java.nio.file.Paths;
15+
import java.util.Map;
16+
import java.util.concurrent.ConcurrentHashMap;
1317

1418
public class ApacheFopJavaResourcesFileResolver implements ResourceResolver {
1519

1620
public static final String FILE_SCHEME = "file";
1721

1822
private static final ResourceResolver defaultFopResolver = ResourceResolverFactory.createDefaultResourceResolver();
1923

24+
private Map<URI, byte[]> javaResourceFileCache = new ConcurrentHashMap<>();
25+
2026
@Override
2127
public Resource getResource(URI uri) throws IOException {
2228
InputStream resourceStream = null;
2329

2430
//We MUST ONLY attempt to handle FILE requests... any Http/Https requests need to use original/default behaviour!
2531
if(uri.getScheme().equalsIgnoreCase(FILE_SCHEME)) {
26-
//Map the requested Uri to the base application path to determine it's relative path as a Resource!
27-
var requestPath = Paths.get(uri);
28-
var mappedPath = ResourceUtils.MapServerPath(requestPath);
29-
30-
//Now with the relative path for our resource we can attempt to retrieve it...
31-
resourceStream = ResourceUtils.LoadResourceAsStream(mappedPath.toString());
32+
resourceStream = findJavaResourceWithCaching(uri);
3233
}
3334

3435
//If the resource was located then we return it...
@@ -48,4 +49,32 @@ public OutputStream getOutputStream(URI uri) throws IOException {
4849
// .openConnection()
4950
// .getOutputStream();
5051
}
52+
53+
//Find the Resource and utilize the internal cache for performance since Embedded Resources can't change without
54+
// deployments to update, we can keep these elements (e.g. Fonts) loaded in Memory for performance!
55+
protected InputStream findJavaResourceWithCaching(URI uri) {
56+
57+
//Use cached results from our Concurrent HashMap cached data if possible!
58+
var resourceBytes = javaResourceFileCache.computeIfAbsent(uri, key -> {
59+
60+
//Map the requested Uri to the base application path to determine it's relative path as a Resource!
61+
var requestPath = Paths.get(uri);
62+
var mappedPath = ResourceUtils.MapServerPath(requestPath);
63+
64+
//Now with the relative path for our resource we can attempt to retrieve it...
65+
var resultStream = ResourceUtils.loadResourceAsStream(mappedPath.toString());
66+
try {
67+
68+
return resultStream != null ? IOUtils.toByteArray(resultStream) : null;
69+
70+
} catch (IOException e) {
71+
//e.printStackTrace();
72+
return null;
73+
}
74+
});
75+
76+
return resourceBytes != null
77+
? new ByteArrayInputStream(resourceBytes)
78+
: null;
79+
}
5180
}

0 commit comments

Comments
 (0)