| <?xml version="1.0" encoding="UTF-8"?> |
| <!-- |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to You under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| <!-- $Id$ --> |
| <!DOCTYPE document PUBLIC "-//APACHE//DTD Documentation V2.0//EN" "http://forrest.apache.org/dtd/document-v20.dtd"> |
| <document> |
| <header> |
| <title>Apache™ FOP: Events/Processing Feedback</title> |
| <version>$Revision$</version> |
| </header> |
| <body> |
| <section id="introduction"> |
| <title>Introduction</title> |
| <p> |
| In versions until 0.20.5, Apache™ FOP used |
| <a href="http://excalibur.apache.org/framework/index.html">Avalon-style Logging</a> where |
| it was possible to supply a logger per processing run. During the redesign |
| the logging infrastructure was switched over to |
| <a href="http://commons.apache.org/logging/">Commons Logging</a> which is (like Log4J or |
| java.util.logging) a "static" logging framework (the logger is accessed through static |
| variables). This made it very difficult in a multi-threaded system to retrieve information |
| for a single processing run. |
| </p> |
| <p> |
| With FOP's event subsystem, we'd like to close this gap again and even go further. The |
| first point is to realize that we have two kinds of "logging". Firstly, we have the logging |
| infrastructure for the (FOP) developer who needs to be able to enable finer log messages |
| for certain parts of FOP to track down a certain problem. Secondly, we have the user who |
| would like to be informed about missing images, overflowing lines or substituted fonts. |
| These messages (or events) are targeted at less technical people and may ideally be |
| localized (translated). Furthermore, tool and solution builders would like to integrate |
| FOP into their own solutions. For example, an FO editor should be able to point the user |
| to the right place where a particular problem occurred while developing a document template. |
| Finally, some integrators would like to abort processing if a resource (an image or a font) |
| has not been found, while others would simply continue. The event system allows to |
| react on these events. |
| </p> |
| <p> |
| On this page, we won't discuss logging as such. We will show how the event subsystem can |
| be used for various tasks. We'll first look at the event subsystem from the consumer side. |
| Finally, the production of events inside FOP will be discussed (this is mostly interesting |
| for FOP developers only). |
| </p> |
| </section> |
| <section id="consumer"> |
| <title>The consumer side</title> |
| <p> |
| The event subsystem is located in the <code>org.apache.fop.events</code> package and its |
| base is the <code>Event</code> class. An instance is created for each event and is sent |
| to a set of <code>EventListener</code> instances by the <code>EventBroadcaster</code>. |
| An <code>Event</code> contains: |
| </p> |
| <ul> |
| <li>an event ID,</li> |
| <li>a source object (which generated the event),</li> |
| <li>a severity level (Info, Warning, Error and Fatal Error) and</li> |
| <li>a map of named parameters.</li> |
| </ul> |
| <p> |
| The <code>EventFormatter</code> class can be used to translate the events into |
| human-readable, localized messages. |
| </p> |
| <p> |
| A full example of what is shown here can be found in the |
| <code>examples/embedding/java/embedding/events</code> directory in the FOP distribution. |
| The example can also be accessed |
| <a href="http://svn.apache.org/viewvc/xmlgraphics/fop/trunk/examples/embedding/java/embedding/events/">via the web</a>. |
| </p> |
| <section id="write-listener"> |
| <title>Writing an EventListener</title> |
| <p> |
| The following code sample shows a very simple EventListener. It basically just sends |
| all events to System.out (stdout) or System.err (stderr) depending on the event severity. |
| </p> |
| <source><![CDATA[import org.apache.fop.events.Event; |
| import org.apache.fop.events.EventFormatter; |
| import org.apache.fop.events.EventListener; |
| import org.apache.fop.events.model.EventSeverity; |
| |
| /** A simple event listener that writes the events to stdout and stderr. */ |
| public class SysOutEventListener implements EventListener { |
| |
| /** {@inheritDoc} */ |
| public void processEvent(Event event) { |
| String msg = EventFormatter.format(event); |
| EventSeverity severity = event.getSeverity(); |
| if (severity == EventSeverity.INFO) { |
| System.out.println("[INFO ] " + msg); |
| } else if (severity == EventSeverity.WARN) { |
| System.out.println("[WARN ] " + msg); |
| } else if (severity == EventSeverity.ERROR) { |
| System.err.println("[ERROR] " + msg); |
| } else if (severity == EventSeverity.FATAL) { |
| System.err.println("[FATAL] " + msg); |
| } else { |
| assert false; |
| } |
| } |
| }]]></source> |
| <p> |
| You can see that for every event the method <code>processEvent</code> of the |
| <code>EventListener</code> will be called. Inside this method you can do whatever |
| processing you would like including throwing a <code>RuntimeException</code>, if you want |
| to abort the current processing run. |
| </p> |
| <p> |
| The code above also shows how you can turn an event into a human-readable, localized |
| message that can be presented to a user. The <code>EventFormatter</code> class does |
| this for you. It provides additional methods if you'd like to explicitly specify |
| the locale. |
| </p> |
| <p> |
| It is possible to gather all events for a whole processing run so they can be |
| evaluated afterwards. However, care should be taken about memory consumption since |
| the events provide references to objects inside FOP which may themselves have |
| references to other objects. So holding on to these objects may mean that whole |
| object trees cannot be released! |
| </p> |
| </section> |
| <section id="add-listener"> |
| <title>Adding an EventListener</title> |
| <p> |
| To register the event listener with FOP, get the <code>EventBroadcaster</code> which |
| is associated with the user agent (<code>FOUserAgent</code>) and add it there: |
| </p> |
| <source><![CDATA[FOUserAgent foUserAgent = fopFactory.newFOUserAgent(); |
| foUserAgent.getEventBroadcaster().addEventListener(new SysOutEventListener());]]></source> |
| <p> |
| Please note that this is done separately for each processing run, i.e. for each |
| new user agent. |
| </p> |
| </section> |
| <section id="listener-example1"> |
| <title>An additional listener example</title> |
| <p> |
| Here's an additional example of an event listener: |
| </p> |
| <p> |
| By default, FOP continues processing even if an image wasn't found. If you have |
| more strict requirements and want FOP to stop if an image is not available, you can |
| do something like the following in the simplest case: |
| </p> |
| <source><![CDATA[public class MyEventListener implements EventListener { |
| |
| public void processEvent(Event event) { |
| if ("org.apache.fop.ResourceEventProducer".equals( |
| event.getEventGroupID())) { |
| event.setSeverity(EventSeverity.FATAL); |
| } else { |
| //ignore all other events (or do something of your choice) |
| } |
| } |
| |
| }]]></source> |
| <p> |
| Increasing the event severity to FATAL will signal the event broadcaster to throw |
| an exception and stop further processing. In the above case, all resource-related |
| events will cause FOP to stop processing. |
| </p> |
| <p> |
| You can also customize the exception to throw (you can may throw a RuntimeException |
| or subclass yourself) and/or which event to respond to: |
| </p> |
| <source><![CDATA[public class MyEventListener implements EventListener { |
| |
| public void processEvent(Event event) { |
| if ("org.apache.fop.ResourceEventProducer.imageNotFound" |
| .equals(event.getEventID())) { |
| |
| //Get the FileNotFoundException that's part of the event's parameters |
| FileNotFoundException fnfe = (FileNotFoundException)event.getParam("fnfe"); |
| |
| throw new RuntimeException(EventFormatter.format(event), fnfe); |
| } else { |
| //ignore all other events (or do something of your choice) |
| } |
| } |
| |
| }]]></source> |
| <p> |
| This throws a <code>RuntimeException</code> with the <code>FileNotFoundException</code> |
| as the cause. Further processing effectively stops in FOP. You can catch the exception |
| in your code and react as you see necessary. |
| </p> |
| </section> |
| </section> |
| <section id="producer"> |
| <title>The producer side (for FOP developers)</title> |
| <p> |
| This section is primarily for FOP and FOP plug-in developers. It describes how to use |
| the event subsystem for producing events. |
| </p> |
| <note> |
| The event package has been designed in order to be theoretically useful for use cases |
| outside FOP. If you think this is interesting independently from FOP, please talk to |
| <a href="mailto:fop-dev@xmlgraphics.apache.org">us</a>. |
| </note> |
| <section id="basic-event-production"> |
| <title>Producing and sending an event</title> |
| <p> |
| The basics are very simple. Just instantiate an <code>Event</code> object and fill |
| it with the necessary parameters. Then pass it to the <code>EventBroadcaster</code> |
| which distributes the events to the interested listeneners. Here's a code example: |
| </p> |
| <source><![CDATA[Event ev = new Event(this, "complain", EventSeverity.WARN, |
| Event.paramsBuilder() |
| .param("reason", "I'm tired") |
| .param("blah", new Integer(23)) |
| .build()); |
| EventBroadcaster broadcaster = [get it from somewhere]; |
| broadcaster.broadcastEvent(ev); |
| ]]></source> |
| <p> |
| The <code>Event.paramsBuilder()</code> is a |
| <a href="http://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a> |
| to help with the build-up of the parameters. You could just as well instantiate a |
| <code>Map</code> (<code>Map<String, Object></code>) and fill it with values. |
| </p> |
| </section> |
| <section id="event-producer"> |
| <title>The EventProducer interface</title> |
| <p> |
| To simplify event production, the event subsystem provides the <code>EventProducer</code> |
| interface. You can create interfaces which extend <code>EventProducer</code>. These |
| interfaces will contain one method per event to be generated. By contract, each event |
| method must have as its first parameter a parameter named "source" (Type Object) which |
| indicates the object that generated the event. After that come an arbitrary number of |
| parameters of any type as needed by the event. |
| </p> |
| <p> |
| The event producer interface does not need to have any implementation. The implementation |
| is produced at runtime by a dynamic proxy created by <code>DefaultEventBroadcaster</code>. |
| The dynamic proxy creates <code>Event</code> instances for each method call against |
| the event producer interface. Each parameter (except "source") is added to the event's |
| parameter map. |
| </p> |
| <p> |
| To simplify the code needed to get an instance of the event producer interface it is |
| suggested to create a public inner provider class inside the interface. |
| </p> |
| <p> |
| Here's an example of such an event producer interface: |
| </p> |
| <source><![CDATA[public interface MyEventProducer extends EventProducer { |
| |
| public class Provider { |
| |
| public static MyEventProducer get(EventBroadcaster broadcaster) { |
| return (MyEventProducer)broadcaster.getEventProducerFor(MyEventProducer.class); |
| } |
| } |
| |
| /** |
| * Complain about something. |
| * @param source the event source |
| * @param reason the reason for the complaint |
| * @param blah the complaint |
| * @event.severity WARN |
| */ |
| void complain(Object source, String reason, int blah); |
| |
| }]]></source> |
| <p> |
| To produce the same event as in the first example above, you'd use the following code: |
| </p> |
| <source><![CDATA[EventBroadcaster broadcaster = [get it from somewhere]; |
| TestEventProducer producer = TestEventProducer.Provider.get(broadcaster); |
| producer.complain(this, "I'm tired", 23);]]></source> |
| </section> |
| <section id="event-model"> |
| <title>The event model</title> |
| <p> |
| Inside an invocation handler for a dynamic proxy, there's no information about |
| the names of each parameter. The JVM doesn't provide it. The only thing you know is |
| the interface and method name. In order to properly fill the <code>Event</code>'s |
| parameter map we need to know the parameter names. These are retrieved from an |
| event object model. This is found in the <code>org.apache.fop.events.model</code> |
| package. The data for the object model is retrieved from an XML representation of the |
| event model that is loaded as a resource. The XML representation is generated using an |
| Ant task at build time (<code>ant resourcegen</code>). The Ant task (found in |
| <code>src/codegen/java/org/apache/fop/tools/EventProducerCollectorTask.java</code>) |
| scans FOP's sources for descendants of the <code>EventProducer</code> interface and |
| uses <a href="http://qdox.codehaus.org/">QDox</a> to parse these interfaces. |
| </p> |
| <p> |
| The event model XML files are generated during build by the Ant task mentioned above when |
| running the "resourcegen" task. So just run <code>"ant resourcegen"</code> if you receive |
| a <code>MissingResourceException</code> at runtime indicating that |
| <code>"event-model.xml"</code> is missing. |
| </p> |
| <p> |
| Primarily, the QDox-based collector task records the parameters' names and types. |
| Furthermore, it extracts additional attributes embedded as Javadoc comments from |
| the methods. At the moment, the only such attribute is "@event.severity" which indicates |
| the default event severity (which can be changed by event listeners). The example event |
| producer above shows the Javadocs for an event method. |
| </p> |
| <p> |
| There's one more information that is extracted from the event producer information for |
| the event model: an optional primary exception. The first exception in the "throws" |
| declaration of an event method is noted. It is used to throw an exception from |
| the invocation handler if the event has an event severity of "FATAL" when all |
| listeners have been called (listeners can update the event severity). Please note |
| that an implementation of |
| <code>org.apache.fop.events.EventExceptionManager$ExceptionFactory</code> has to be |
| registered for the <code>EventExceptionManager</code> to be able to construct the |
| exception from an event. |
| </p> |
| <p> |
| For a given application, there can be multiple event models active at the same time. |
| In FOP, each renderer is considered to be a plug-in and provides its own specific |
| event model. The individual event models are provided through an |
| <code>EventModelFactory</code>. This interface is implemented for each event model |
| and registered through the service provider mechanism |
| (see the <a href="#plug-ins">plug-ins section</a> for details). |
| </p> |
| </section> |
| <section id="event-severity"> |
| <title>Event severity</title> |
| <p> |
| Four different levels of severity for events has been defined: |
| </p> |
| <ol> |
| <li>INFO: informational only</li> |
| <li>WARN: a Warning</li> |
| <li>ERROR: an error condition from which FOP can recover. FOP will continue processing.</li> |
| <li>FATAL: a fatal error which causes an exception in the end and FOP will stop processing.</li> |
| </ol> |
| <p> |
| Event listeners can choose to ignore certain events based on their event severity. |
| Please note that you may recieve an event "twice" in a specific case: if there is |
| a fatal error an event is generated and sent to the listeners. After that an exception |
| is thrown with the same information and processing stops. If the fatal event is |
| shown to the user and the following exception is equally presented to the user it |
| may appear that the event is duplicated. Of course, the same information is just |
| published through two different channels. |
| </p> |
| </section> |
| <section id="plug-ins"> |
| <title>Plug-ins to the event subsystem</title> |
| <p> |
| The event subsystem is extensible. There are a number of extension points: |
| </p> |
| <ul> |
| <li> |
| <strong><code>org.apache.fop.events.model.EventModelFactory</code>:</strong> Provides |
| an event model to the event subsystem. |
| </li> |
| <li> |
| <strong><code>org.apache.fop.events.EventExceptionManager$ExceptionFactory</code>:</strong> |
| Creates exceptions for events, i.e. turns an event into a specific exception. |
| </li> |
| </ul> |
| <p> |
| The names in bold above are used as filenames for the service provider files that |
| are placed in the <code>META-INF/services</code> directory. That way, they are |
| automatically detected. This is a mechanism defined by the |
| <a href="http://java.sun.com/j2se/1.4.2/docs/guide/jar/jar.html#Service%20Provider">JAR file specification</a>. |
| </p> |
| </section> |
| <section id="l10n"> |
| <title>Localization (L10n)</title> |
| <p> |
| One goal of the event subsystem was to have localized (translated) event messages. |
| The <code>EventFormatter</code> class can be used to convert an event to a |
| human-readable message. Each <code>EventProducer</code> can provide its own XML-based |
| translation file. If there is none, a central translation file is used, called |
| "EventFormatter.xml" (found in the same directory as the <code>EventFormatter</code> |
| class). |
| </p> |
| <p> |
| The XML format used by the <code>EventFormatter</code> is the same as |
| <a href="ext:cocoon">Apache Cocoon's</a> catalog format. Here's an example: |
| </p> |
| <source><![CDATA[<?xml version="1.0" encoding="UTF-8"?> |
| <catalogue xml:lang="en"> |
| <message key="locator"> |
| [ (See position {loc})| (See {#gatherContextInfo})| (No context info available)] |
| </message> |
| <message key="org.apache.fop.render.rtf.RTFEventProducer.explicitTableColumnsRequired"> |
| RTF output requires that all table-columns for a table are defined. Output will be incorrect.{{locator}} |
| </message> |
| <message key="org.apache.fop.render.rtf.RTFEventProducer.ignoredDeferredEvent"> |
| Ignored deferred event for {node} ({start,if,start,end}).{{locator}} |
| </message> |
| </catalogue> |
| ]]></source> |
| <p> |
| The example (extracted from the RTF handler's event producer) has message templates for |
| two event methods. The class used to do variable replacement in the templates is |
| <code>org.apache.fop.util.text.AdvancedMessageFormat</code> which is more powerful |
| than the <code>MessageFormat</code> classes provided by the Java class library |
| (<code>java.util.text</code> package). |
| </p> |
| <p> |
| "locator" is a template that is reused by the other message templates |
| by referencing it through "{{locator}}". This is some kind of include command. |
| </p> |
| <p> |
| Normal event parameters are accessed by name inside single curly braces, for example: |
| "{node}". For objects, this format just uses the <code>toString()</code> method to turn |
| the object into a string, unless there is an <code>ObjectFormatter</code> registered |
| for that type (there's an example for <code>org.xml.sax.Locator</code>). |
| </p> |
| <p> |
| The single curly braces pattern supports additional features. For example, it is possible |
| to do this: "{start,if,start,end}". "if" here is a special field modifier that evaluates |
| "start" as a boolean and if that is true returns the text right after the second comma |
| ("start"). Otherwise it returns the text after the third comma ("end"). The "equals" |
| modifier is similar to "if" but it takes as an additional (comma-separated) parameter |
| right after the "equals" modifier, a string that is compared to the value of the variable. |
| An example: {severity,equals,EventSeverity:FATAL,,some text} (this adds "some text" if |
| the severity is not FATAL). |
| </p> |
| <p> |
| Additional such modifiers can be added by implementing the |
| <code>AdvancedMessageFormat$Part</code> and <code>AdvancedMessageFormat$PartFactory</code> |
| interfaces. |
| </p> |
| <p> |
| Square braces can be used to specify optional template sections. The whole section will |
| be omitted if any of the variables used within are unavailable. Pipe (|) characters can |
| be used to specify alternative sub-templates (see "locator" above for an example). |
| </p> |
| <p> |
| Developers can also register a function (in the above example: |
| <code>{#gatherContextInfo})</code> |
| to do more complex information rendering. These functions are implementations of the |
| <code>AdvancedMessageFormat$Function</code> interface. Please take care that this is |
| done in a locale-independent way as there is no locale information available, yet. |
| </p> |
| </section> |
| </section> |
| </body> |
| </document> |