Status

Mostly implemented, still on a temporary branch. Details: ProcessingFeedback Status

What we have

  • Commons Logging
  • ElementListObserver (useful for developers only)
  • FormattingResults (recently ported from 0.20.5)

Problems

Commons Logging logs through static variables so it is difficult to separate feedback in a multi-threaded environment. Also, logging is actually mostly useful for developers, not necessarily for users. Verbose settings from the command-line will now produce a lot of alien-looking stuff.

FormattingResults is only a very simple solution. It fits some people's needs but is limited.

Further needs and wishes

  • Call backs from inside the layout engine alerting the calling application about layout problems such as overflows, missing resources, validation warnings and errors.
  • Some applications need to be stricter about layout problems and may choose to veto some warnings which should effectively throw an exception and halt processing.
  • FO editor writers will want to provide better feedback during design/preview time for their users when problems arise. They need some sort of location and context information that can be given back.
  • In mass document production environment it is important to have a mechanisms in place that allow identifying anomalies.
  • For machine-interpretation all events should have an ID and for human-interpretation feedback from FOP should eventually be translated in the user's own language because many error messages may be difficult to understand.
  • For some embedded use cases, additional hooks for providing images as BufferedImages or via Graphics2D would be very nice as it could spare some people writing a FOP extension. We already have a stream insertion (or rather custom URI resolution) hook in FOUserAgent.
  • User-oriented feedback from FOP in general should be available per processing run (as opposed to debugging output currently provided by Commons Logging).
  • The layout engine should be able to provide some information per-page, for example: An important information for users producing book-style documents is, for each page, the difference between the available page heigth and the actual content height. Even if it is not a real "error" to create unfilled pages, underfull pages are not beautiful. This information, that could be taken from the PageBreakPositions, would allow users to easily check if fop produced "good" pages without the need to open the pdf and look at it.
  • Furthermore, editor writers might be interested to know what IDs ended up on which page to provide quick "jump-to-problem" functions, for example.
  • Messages should be localized. At least English messages will be provided by FOP and it should be easy for users to contribute translations with a fallback to English messages if a translation is missing. Ideally, there's a check mechanism that makes sure there's at least an English message for each event/message.

Brainstorming for event types

(Note: this is not a complete listing, just exemplary!)

  • Validation problems
    • Invalid child (Params: context element, location)
    • Missing child element (Params: context element, location)
    • Missing property (Params: property name, context element, location)
    • Nodes out of order (Params: context element, tooLateNode, tooEarlyNode, location)
    • Too many nodes
    • Invalid property name
    • etc. etc. etc.
  • Layout problems
    • Line overflow (warning or error if overflow="error-if-overflow")
    • Page overflow (warning or error if overflow="error-if-overflow")
    • Various table-related warning (such as "column-width not set" or "row-height constraint violation")
    • etc. etc. etc.
  • Resource problems
    • Image not found (Params: URI, context element, location)
    • Image provision/conversion problem (Params: URI, context element, location)
    • Font loading problem (Params: URI)
    • Configuration error
    • etc. etc. etc.
  • Rendering problems
    • Unresolved destination (Params: ID)
    • Paper selection problem (PCL, Params: page number, page size, fallback paper type)
    • etc. etc. etc.
  • Progress
    • Document started
    • Start page-sequence (Params: ID)
    • End page-sequence (Params: ID, statistics)
    • Page produced (i.e. unresolved, Params: page index/number)
    • Page completed (i.e. fully resolved and sent to renderer, Params: page index/number)
    • Document complete (Params: statistics)

Many of these events are considered warnings by some users and errors by others. Some don't care about overflows while others want to know about it. Thus the need for being able to throw an Exception on some events/event categories.

Suggestions

Following are 3 different possible solutions that were proposed. Implementation for the "Extended Approach" has started on a branch.

Blammo

I would like to propose using something like Blammo. Blammo is extremely simple. You simply define an interface in which all of the operations represent event conditions in which you are interested. I am not aware of the specific conditions that you might want to track, but gathering from the above, I could imagine something like this:

interface FopLogger {

  void logMissingResource(String path);

  void logInvalidFoElement(String xpath);

  ...
}

Then you use classic JavaDoc annotations how this should be bound to a lower-level logging API:

interface FopLogger {

  /**
   * @blammo.level warn
   * @blammo.message Failed to locate resource {path}
   */
  void logMissingResource(String path);

  /**
   * @blammo.level error
   * @blammo.message Invalid FO content located here: {xpath}
   */
  void logInvalidFoElement(String xpath);

  ...
}

In the FOP code, you would instantiate an implementation of the logger using the BlammoLoggerFactory.create(...) operation. This will construct an implementation that is bound to a lower level logging API, which could be the one of your choice. In my case - I want to wire it to the Maven plugin logger. So I would pass in a LoggingKitAdapter that would map all logging to the Maven logger.

There are a couple of benefits for dealing with it like this:

  • You can always trap specific conditions yourself in your own code, by implementing your own implementation of the interface, or a proxy that forwards to the Blammo generated implementation.
  • Blammo is generating message IDs.
  • Blammo generates an ID to human readable error description, as a Maven report.
  • It keeps the calling code relatively readable. (No if (log.isDebugEnabled()) stuff, etc., no message formatting.)
  • You really program to an interface, instead of an implementation.

Drawbacks:

  • Unusable for us until the license is changed from LGPL to something that's on the allowed licenses list: http://www.apache.org/legal/3party.html
  • Needs Maven for code generation. No Ant task available, yet.
  • Localization of the error messages doesn't seem to be available, yet.

Comment by JeremiasMaerki: Thanks, Wilfred, for your suggestion and for writing this up. But I don't think it's going to happen this way in the short term because of the above points. Furthermore, I'm very allergic to Maven.

Comment by WilfredSpringer: I could change the license, and the annotations are JavaDoc-type of annotations. Think commons-attributes. In this case it's based on QDox, so you don't have to worry about Java 1.5.

Comment by JeremiasMaerki: I've removed the point about Java 1.5. You were right, of course. I just saw "@"s and thought they were annotations. I took a closer look now. A problem for us would be the current necessity for Maven for code generation. We'd need an Ant task. I get the impression that the main focus is on logging those events to a logging subsystem. For us, I think, this would only be a side-show. The most important is for the user to install an event listener so all events (including all parameters) can be inspected and acted upon (including throwing exceptions). My proposal: I'm going to sketch out a proposal (without Blammo) that I think would meet the requirements of FOP and then we can see if and how Blammo could maybe accommodate this and if it would be worth adding such a dependency.

Minimal Approach

package org.apache.fop.events;

public class Event extends EventObject {

    [..]

    public Event(Object source, String eventID, Map params) {
    [..]

}

public interface EventListener extends java.util.EventListener {
    void processEvent(Event event);
}


public interface EventBroadcaster {
    void addEventListener(EventListener listener);
    void removeEventListener(EventListener listener);
    int getListenerCount();
    void broadcastEvent(Event event);
}

Sample code:

    MyEventListener listener = new MyEventListener();

    EventBroadcaster broadcaster = new DefaultEventBroadcaster();
    broadcaster.addEventListener(listener);

    [..]

    public static final String EVENT_MISSING_CHILD_ERROR = "missing-child-error";

    [..]
        
    Event ev = new Event(this, EventConstants.EVENT_MISSING_CHILD_ERROR,
                Event.paramsBuilder()
                    .param("element", this)
                    .param("elementName", getName())
                    .param("contentModel", contentModel)
                    .param("locator", this.locator)
                    .build());
    broadcaster.broadcastEvent(ev);

Notes:

  • A special EventListener can be written and plugged-in which creates human-readable messages from the event objects that can be logged using the default logger.
  • Message templates would be saves in properties files, accessed by ResourceBundle.

Good:

  • Easy to implement, not much infrastructure necessary
  • No external dependencies

Bad:

  • Event production not as nice and clean as with a Java interface
  • No check mechanisms for detecting missing messages/message translations
  • No check mechanisms for detecting mismatches between used parameters and the parameters that are available

Extended Approach

This one is based on the above and extends it to address the negative points. The basic interfaces above remain unchanged. It uses similar ideas like the Blammo proposal but tries to address the negative points, too.

A marker interface is created:

public interface EventProducer {
}

Multiple interfaces deriving from EventProducer are created, for example:

public interface BasicFOValidationEventProducer extends EventProducer {
    [..]

    /**
     * @event.severity ERROR
     */
    public void missingChild(Object source,
        FObj element, String elementName, String contentModel, Locator locator);

    [..]

QDox will be used to parse all Java interfaces which extend from EventProducer (as part of an Ant task). For each method in every interface an event definition object will be built that contains all meta-information about the object. This meta model will be written to an XML file. QDox will become a build-time dependency only.

Comment by Vincent Hennebert: once we upgrade to Java 1.5 as a minimum requirement this should be possible to replace those Javadoc annotations with 1.5 annotations and switch back to a standard mechanism to generate the classes.

''Comment by JeremiasMaerki: In general, I agree but annotations will not make the method parameter names accessible. Reflection doesn't offer that, either. If we go this way, QDox will likely remain for a while.

Using XSLT (or using Java, TBD) and based on that XML file another XML is generated which contains structures for entering the message templates. An existing translation file is merged automatically so existing translations aren't lost. The same also happens for any existing files for other languages. A unit test will make sure there is at least an English message template for each event. The event ID for each event is derived from the interface and method name.

At run-time the XML translation files are read into memory and made available similar as with ResourceBundle (actually a subclass of that class). XML for the translation files is used to avoid the awkward way special characters need to be escaped and to address the fact the properties files don't have an encoding indicator like XML files do.

Message production will not be done using java.text.MessageFormat (which uses parameter indexes). Instead the parameter names defined on the interface can be used directly as message parameters which should make the whole thing more readable and less error-prone.

The implementations for the EventProducer interfaces will be provided as dynamic proxies which will make Java code generation unnecessary. Since the necessary metadata can be extracted from the XML file created at code generation time it becomes possible to map the individual event parameters into the Map of the Event object (Reflection doesn't provide the names of the method parameters).

Acquisition of an EventProducer and event production:

    BasicFOValidationEventProducer producer =
      (BasicFOValidationEventProducer)getUserAgent().getEventBroadcaster().getEventProducerFor(
         BasicFOValidationEventProducer.class);

    producer.missingChild(this, this, getName(), "marker* (%block;)+", locator);

EventBroadcaster will create and cache the dynamic proxies implementing the various EventProducer interfaces. For this, the EventBroadcaster is extended by the above method (compared to the first approach). This should all be quite fast once all the translations have been loaded.

Good:

  • Only new build-time dependencies (QDox, ALv2)
  • Good type-safety and consistency/completeness checking for the translations
  • No Java code generation necessary

Bad:

  • More complex to implement
  • No labels