|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- <?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>
|