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
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)