| <html><head> |
| <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
| <title xmlns:d="http://docbook.org/ns/docbook">Chapter 10. Lifecycle Events</title><link rel="stylesheet" type="text/css" href="css/cayenne-doc.css"><meta xmlns:d="http://docbook.org/ns/docbook" name="keywords" content="Cayenne 3.1 documentation"><meta xmlns:d="http://docbook.org/ns/docbook" name="description" content="User documentation for Apache Cayenne version 3.1"><link rel="home" href="index.html" title="Cayenne Guide"><link rel="up" href="cayenne-guide-part2.html" title="Part II. Cayenne Framework"><link rel="prev" href="queries.html" title="Chapter 9. Queries"><link rel="next" href="performance-tuning.html" title="Chapter 11. Performance Tuning"><script xmlns:d="http://docbook.org/ns/docbook" type="text/javascript"> |
| var _gaq = _gaq || []; |
| _gaq.push(['_setAccount', 'UA-7036673-1']); |
| _gaq.push(['_trackPageview']); |
| (function() { |
| var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; |
| ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; |
| var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); |
| })(); |
| </script></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div xmlns:d="http://docbook.org/ns/docbook" class="navheader"><table width="100%" summary="Navigation header"><tr><th class="versioninfo">v.3.1 (3.1)</th><th align="center">Chapter 10. Lifecycle Events</th><th></th></tr><tr><td width="20%" align="left"><a accesskey="p" href="queries.html">Prev</a> </td><th width="60%" align="center"><a accesskey="u" href="cayenne-guide-part2.html">Part II. Cayenne Framework</a></th><td width="20%" align="right"> <a accesskey="n" href="performance-tuning.html">Next</a></td></tr></table><hr></div><div class="chapter" title="Chapter 10. Lifecycle Events"><div class="titlepage"><div><div><h2 class="title"><a name="lifecycle-events"></a>Chapter 10. Lifecycle Events</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="lifecycle-events.html#types-of-lifecycle-events">Types of Lifecycle Events</a></span></dt><dt><span class="section"><a href="lifecycle-events.html#callback-persistent">Callbacks on Persistent Objects</a></span></dt><dt><span class="section"><a href="lifecycle-events.html#callback-non-persistent">Callbacks on Non-Persistent Listeners</a></span></dt><dt><span class="section"><a href="lifecycle-events.html#comining-listeners-with-datachannelfilters">Combining Listeners with DataChannelFilters</a></span></dt></dl></div><p>An application might be interested in getting notified when a Persistent object moves |
| through its lifecycle (i.e. fetched from DB, created, modified, committed). E.g. when a new |
| object is created, the application may want to initialize its default properties (this can't |
| be done in constructor, as constructor is also called when an object is fetched from DB). |
| Before save, the application may perform validation and/or set some properties (e.g. |
| "updatedTimestamp"). After save it may want to create an audit record for each saved object, |
| etc., etc. </p><p>All this can be achieved by declaring callback methods either in Persistent objects or in |
| non-persistent listener classes defined by the application (further simply called |
| "listeners"). There are eight types of lifecycle events supported by Cayenne, listed later |
| in this chapter. When any such event occurs (e.g. an object is committed), Cayenne would |
| invoke all appropriate callbacks. Persistent objects would receive their own events, while |
| listeners would receive events from any objects. </p><p>Cayenne allows to build rather powerful and complex "workflows" or "processors" tied to |
| objects lifecycle, especially with listeners, as they have full access to the application |
| evnironment outside Cayenne. This power comes from such features as filtering which entity |
| events are sent to a given listener and the ability to create a common operation context for |
| multiple callback invocations. All of these are discussed later in this chapter.</p><div class="section" title="Types of Lifecycle Events"><div class="titlepage"><div><div><h2 class="title"><a name="types-of-lifecycle-events"></a>Types of Lifecycle Events</h2></div></div></div><p>Cayenne defines the following 8 types of lifecycle events for which callbacks can be |
| regsitered:</p><table frame="void" id="d0e1953"><caption>Table 10.1. Lifecycle Event Types</caption><col width="16%"><col width="84%"><thead><tr> |
| <th>Event</th> |
| <th>Occurs...</th> |
| </tr></thead><tbody><tr> |
| <td>PostAdd</td> |
| <td>right after a new object is created inside |
| <code class="code">ObjectContext.newObject()</code>. When this event is fired the |
| object is already registered with its ObjectContext and has its ObjectId |
| and ObjectContext properties set.</td> |
| </tr><tr> |
| <td>PrePersist</td> |
| <td>right before a new object is committed, inside |
| <code class="code">ObjectContext.commitChanges()</code> and |
| <code class="code">ObjectContext.commitChangesToParent()</code> (and prior to |
| "<code class="code">validateForInsert()</code>").</td> |
| </tr><tr> |
| <td>PreUpdate</td> |
| <td>right before a modified object is committed, inside |
| <code class="code">ObjectContext.commitChanges()</code> and |
| <code class="code">ObjectContext.commitChangesToParent()</code> (and prior to |
| "<code class="code">validateForUpdate()</code>").</td> |
| </tr><tr> |
| <td>PreRemove</td> |
| <td>right before an object is deleted, inside |
| <code class="code">ObjectContext.deleteObjects()</code>. The event is also |
| generated for each object indirectly deleted as a result of CASCADE |
| delete rule.</td> |
| </tr><tr> |
| <td>PostPersist</td> |
| <td>right after a commit of a new object is done, inside |
| <code class="code">ObjectContext.commitChanges()</code>.</td> |
| </tr><tr> |
| <td>PostUpdate</td> |
| <td>right after a commit of a modified object is done, inside |
| <code class="code">ObjectContext.commitChanges()</code>.</td> |
| </tr><tr> |
| <td>PostRemove</td> |
| <td>right after a commit of a deleted object is done, inside |
| <code class="code">ObjectContext.commitChanges()</code>.</td> |
| </tr><tr> |
| <td>PostLoad</td> |
| <td> |
| <div class="itemizedlist"><ul class="itemizedlist"><li class="listitem"><p>After an object is fetched inside |
| <code class="code">ObjectContext.performQuery()</code>.</p></li><li class="listitem"><p>After an object is reverted inside |
| <code class="code">ObjectContext.rollbackChanges()</code>.</p></li><li class="listitem"><p>Anytime a faulted object is resolved (i.e. if a |
| relationship is fetched).</p></li></ul></div> |
| </td> |
| </tr></tbody></table></div><div class="section" title="Callbacks on Persistent Objects"><div class="titlepage"><div><div><h2 class="title"><a name="callback-persistent"></a>Callbacks on Persistent Objects</h2></div></div></div><p>Callback methods on Persistent classes are mapped in CayenneModeler for each |
| ObjEntity. Empty callback methods are automatically created as a part of class |
| generation (either with Maven, Ant or the Modeler) and are later filled with appropriate |
| logic by the programmer. E.g. assuming we mapped a 'post-add' callback called |
| 'onNewOrder' in ObjEntity 'Order', the following code will be |
| generated:</p><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">abstract</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span> _Order <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">extends</span> CayenneDataObject { |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">protected</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">abstract</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> onNewOrder(); |
| } |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span> Order <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">extends</span> _Order { |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@Override</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">protected</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> onNewOrder() { |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">//TODO: implement onNewOrder</span> |
| } |
| }</pre><p>As <code class="code">onNewOrder()</code> is already declared in the mapping, it does not need to |
| be registered explicitly. Implementing the method in subclass to do something meaningful |
| is all that is required at this point. </p><p>As a rule callback methods do not have any knowledge of the outside application, and |
| can only access the state of the object itself and possibly the state of other |
| persistent objects via object's own ObjectContext.</p><p> |
| </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p><span class="italic">Validation and callbacks:</span> There is a clear |
| overlap in functionality between object callbacks and |
| <code class="code">DataObject.validateForX()</code> methods. In the future validation may |
| be completely superceeded by callbacks. It is a good idea to use "validateForX" |
| strictly for validation (or not use it at all). Updating the state before commit |
| should be done via callbacks.</p></div><p> |
| </p></div><div class="section" title="Callbacks on Non-Persistent Listeners"><div class="titlepage"><div><div><h2 class="title"><a name="callback-non-persistent"></a>Callbacks on Non-Persistent Listeners</h2></div></div></div><p> |
| </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>While listener callback methods can be declared in the Modeler (at least |
| as of this wrting), which ensures their automatic registration in runtime, |
| there's a big downside to it. The power of the listeners lies in their |
| complete separation from the XML mapping. The mapping once created, can be |
| reused in different contexts each having a different set of listeners. |
| Placing a Java class of the listener in the XML mapping, and relying on |
| Cayenne to instantiate the listeners severly limits mapping reusability. |
| Further down in this chapter we'll assume that the listener classes are |
| never present in the DataMap and are registered via API.</p></div><p> |
| </p><p>A listener is simply some application class that has one or more annotated |
| callback methods. A callback method signature should be <code class="code">void |
| someMethod(SomePersistentType object)</code>. It can be public, private, protected |
| or use default access:</p><p> |
| </p><pre class="programlisting"> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span> OrderListener { |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostAdd(Order.class)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> setDefaultsForNewOrder(Order o) { |
| o.setCreatedOn(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> Date()); |
| } |
| }</pre><p> |
| </p><p>Notice that the example above contains an annotation on the callback method that |
| defines the type of the event this method should be called for. Before we go into |
| annotation details, we'll show how to create and register a listener with Cayenne. It is |
| always a user responsibility to register desired application listeners, usually right |
| after ServerRuntime is started. Here is an example:</p><p>First let's define 2 simple |
| listeners.</p><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span> Listener1 { |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostAdd(MyEntity.class)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> postAdd(Persistent object) { |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// do something</span> |
| } |
| } |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span> Listener2 { |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostRemove({ MyEntity1.class, MyEntity2.class })</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> postRemove(Persistent object) { |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// do something</span> |
| } |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostUpdate({ MyEntity1.class, MyEntity2.class })</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> postUpdate(Persistent object) { |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// do something</span> |
| } |
| }</pre><p>Ignore the annotations for a minute. The important point here is that the listeners |
| are arbitrary classes unmapped and unknown to Cayenne, that contain some callback |
| methods. Now let's register them with |
| runtime:</p><pre class="programlisting">ServerRuntime runtime = ... |
| |
| LifecycleCallbackRegistry registry = |
| runtime.getDataDomain().getEntityResolver().getCallbackRegistry(); |
| |
| registry.addListener(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> Listener1()); |
| registry.addListener(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> Listener2());</pre><p>Listeners in this example are very simple. However they don't have to be. Unlike |
| Persistent objects, normally listeners initialization is managed by the application |
| code, not Cayenne, so listeners may have knowledge of various application services, |
| operation transactional context, etc. Besides a single listener can apply to multiple |
| entities. As a consequence their callbacks can do more than just access a single |
| ObjectContext. </p><p>Now let's discuss the annotations. There are eight annotations exactly matching the |
| names of eight lifecycle events. A callback method in a listener should be annotated |
| with at least one, but possibly with more than one of them. Annotation itself defines |
| what event the callback should react to. Annotation parameters are essentially an entity |
| filter, defining a subset of ObjEntities whose events we are interested |
| in:</p><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// this callback will be invoked on PostRemove event of any object </span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// belonging to MyEntity1, MyEntity2 or their subclasses</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostRemove({ MyEntity1.class, MyEntity2.class })</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> postRemove(Persistent object) { |
| ... |
| }</pre><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// similar example with multipe annotations on a single method</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// each matching just one entity</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostPersist(MyEntity1.class)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostRemove(MyEntity1.class)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostUpdate(MyEntity1.class)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> postCommit(MyEntity1 object) { |
| ... |
| }</pre><p>As shown above, "value" (the implicit annotation parameter) can contain one or more |
| entity classes. Only these entities' events will result in callback invocation. There's |
| also another way to match entities - via custom annotations. This allows to match any |
| number of entities without even knowing what they are. Here is an example. We'll first |
| define a custom |
| annotation:</p><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@Target(ElementType.TYPE)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@Retention(RetentionPolicy.RUNTIME)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@interface</span> Tag { |
| |
| }</pre><p>Now we can define a listener that will react to events from ObjEntities annotated with |
| this |
| annotation:</p><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span> Listener3 { |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostAdd(entityAnnotations = Tag.class)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> postAdd(Persistent object) { |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// do something</span> |
| } |
| }</pre><p>As you see we don't have any entities yet, still we can define a listener that does |
| something useful. Now let's annotate some |
| entities:</p><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@Tag</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span> MyEntity1 <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">extends</span> _MyEntity1 { |
| |
| } |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@Tag</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span> MyEntity2 <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">extends</span> _MyEntity2 { |
| |
| }</pre></div><div class="section" title="Combining Listeners with DataChannelFilters"><div class="titlepage"><div><div><h2 class="title"><a name="comining-listeners-with-datachannelfilters"></a>Combining Listeners with DataChannelFilters</h2></div></div></div><p>A final touch in the listeners design is preserving the state of the listener within a |
| single select or commit, so that events generated by multiple objects can be collected |
| and processed all together. To do that you will need to implement a |
| <code class="code">DataChannelFilter</code>, and add some callback methods to it. They will store |
| their state in a ThreadLocal variable of the filter. Here is an example filter that does |
| something pretty meaningless - counts how many total objects were committed. However it |
| demonstrates the important pattern of aggregating multiple events and presenting a |
| combined |
| result:</p><pre class="programlisting"><span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">class</span> CommittedObjectCounter <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">implements</span> DataChannelFilter { |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">private</span> ThreadLocal<<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">int</span>[]> counter; |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@Override</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> init(DataChannel channel) { |
| counter = <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> ThreadLocal<<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">int</span>[]>(); |
| } |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@Override</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) { |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">return</span> filterChain.onQuery(originatingContext, query); |
| } |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@Override</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">public</span> GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">int</span> syncType, |
| DataChannelFilterChain filterChain) { |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// init the counter for the current commit</span> |
| counter.set(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">int</span>[<span xmlns="http://www.w3.org/1999/xhtml" class="hl-number">1</span>]); |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">try</span> { |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">return</span> filterChain.onSync(originatingContext, changes, syncType); |
| } <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">finally</span> { |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// process aggregated result and release the counter</span> |
| System.out.println(<span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">"Committed "</span> + counter.get()[<span xmlns="http://www.w3.org/1999/xhtml" class="hl-number">0</span>] + <span xmlns="http://www.w3.org/1999/xhtml" class="hl-string">" object(s)"</span>); |
| counter.set(null); |
| } |
| } |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostPersist(entityAnnotations = Tag.class)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostUpdate(entityAnnotations = Tag.class)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-annotation">@PostRemove(entityAnnotations = Tag.class)</span> |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">void</span> afterCommit(Persistent object) { |
| counter.get()[<span xmlns="http://www.w3.org/1999/xhtml" class="hl-number">0</span>]++; |
| } |
| }</pre><p>Now since this is both a filter and a listener, it needs to be registered as |
| such:</p><pre class="programlisting">CommittedObjectCounter counter = <span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">new</span> CommittedObjectCounter(); |
| |
| ServerRuntime runtime = ... |
| DataDomain domain = runtime.getDataDomain(); |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// register filter</span> |
| domain.addFilter(counter); |
| |
| <span xmlns="http://www.w3.org/1999/xhtml" class="hl-comment">// register listener</span> |
| domain.getEntityResolver().getCallbackRegistry().addListener(counter);</pre></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="queries.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="cayenne-guide-part2.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="performance-tuning.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Chapter 9. Queries </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 11. Performance Tuning</td></tr></table></div></body></html> |