Status

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

What we have

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

Brainstorming for event types

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

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:

Drawbacks:

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:

Good:

Bad:

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.

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

ProcessingFeedback (last edited 2009-09-20 23:52:40 by localhost)