Proposed changes to Fop configuration and deployment (the o.a.f.apps API)
The following is a proposal to modify some parts of the FOP API that are used for configuring and executing the FOP process (e.g fo -> pdf). The change is designed to make the separation of configuration and deployment both explicit and enforceable.
The FOP process is executed using an instance of o.a.f.apps.Fop, and typically triggered from an XSLT transform:
transformer.transform(new StreamSource(inputFoFile), fop.getDefaultHandler())
Many configurable options are available to customize the process. e.g. enabling accessibility in PDF output. Configuration can be shared amongst different instances of FOP, and this is coordinated with a FopFactory and a FOUserAgent. To describe how these classes collaborate, lets consider a fairly general use case
FopFactory fopFactory = FopFactory.newInstance();
//Triggers a parse of the fop conf file and the setting of properties on the fopFactory
FOUserAgent foUserAgent = fopFactory.newFOUserAgent();
Fop fop = fopFactory.newFop(outputFormat, foUserAgent, mimeType);
// trigger FOP process
The Fop object has access to the FOUserAgent and the FopFactory (exposed by FOUserAgent)
From line 4 we see that the is a one-to-many relationship between FopFactory and FOUserAgent; The intended use of this API is to create one FOUserAgent for each Fop instance, however there is no restriction in place to ensure this, neither intent to change this behaviour in the near future.
The code demonstrates that the configuration of the FopFactory can be interleaved with the construction of the Fop instance, and furthermore, the internal FOP process can change properties of the FopFactory (and FOUserAgent) and making assertions about the state of Fop is made difficult.
We propose a change to the API to impose that the configuration and creation of Fop is done in a strict sequential order. To orchestrate this we have introduced a few new classes, notably FopConfParser and FopFactoryBuilder (plus others explained below). An example best demonstrates this:
// Parse the fopConf, setting properties on the FopFactoryBuilder
FopFactoryBuilder fopFactoryBuilder = new FopConfParser(userConfigFile).getFopFactoryBuilder()
FopFactory fopFactory = fopFactoryBuilder.build();
fopFactoryBuilder.setX(x) // ! throws an IllegalStateException !
Note that now, once built, the FopFactory properties cannot be reassigned (although they may be references to mutable data, of course).
The FOUserAgent no longer provides access to the FopFactory, but instead provides the read-only properties directly ( FOUserAgent.getFopFactory().getX() becomes FOUserAgent.getX(). Further more, for backwards compatibility the static members newFop(String) and newFop(String, OutputStream) have been added (these are required by o.a.f.cli.InputHandler). Other than than the removal of getFopFactory(), changes to FOUserAgent will have no impact upon Fop client code.
At this stage, other than the FopFactory (and possible URI resolution related ones)), access to properties that are exclusive to the FOUserAgent will not change.
public static FopFactory newInstance(String fopConfFile);
public static FopFactory newInstance(URI baseURI);
The last method is introduced as part of Unifying URI Resolution
Single configuration, single run
Currently FOP trunk reads the "generic" information when the FopFactory is instantiated, but postpones the reading of renderer specific configuration until it is necessary (i.e. when a FOP run is invoked). The configuration information isn't cached, so the renderer-specific config info is read on every FOP run. This isn't a major problem on the command-line however, in an embedded environment this can be costly. Presumably, some of this cost was mitigated by creating a font-caching system. However, in a highly restricted environment, FOP may not be allowed to create serialized caches in a temporary place for a variety of reasons.
The solution to this is fairly simple, to redesign the configuration to favour the embedded use-case and allow for the CLI font-caching as well. We have done this by caching all the parsed FOP conf information into an object that is controlled by the FOUserAgent. This is a lazy loaded cache so that the renderer specific config is only parsed when that renderer is invoked, however, once invoked, that specific config will not be parsed again. This has an additional benefit of making the font cache redundant in the embedded use case, since costs of parsing config is only done once in either case.
However, the font-cache isn't made redundant in the CLI use-case since that config doesn't persist. As such we kept the font-cache mechanism albeit with changes to how the font-caching set-up works which will be discussed further.
Is there a use case for setting properties on the FopFactoryBuilder before parsing the fopConf? - this is a trivial change left out to simplify the API: we would just need to implement new FopConfParser(args..., FopFactoryBuilder)