blob: 1600400a608b557da1b5b01072ca40df0e18363a [file] [log] [blame]
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title xmlns:d="http://docbook.org/ns/docbook">Chapter&nbsp;10.&nbsp;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&nbsp;II.&nbsp;Cayenne Framework"><link rel="prev" href="queries.html" title="Chapter&nbsp;9.&nbsp;Queries"><link rel="next" href="performance-tuning.html" title="Chapter&nbsp;11.&nbsp;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&nbsp;10.&nbsp;Lifecycle Events</th><th></th></tr><tr><td width="20%" align="left"><a accesskey="p" href="queries.html">Prev</a>&nbsp;</td><th width="60%" align="center"><a accesskey="u" href="cayenne-guide-part2.html">Part&nbsp;II.&nbsp;Cayenne Framework</a></th><td width="20%" align="right">&nbsp;<a accesskey="n" href="performance-tuning.html">Next</a></td></tr></table><hr></div><div class="chapter" title="Chapter&nbsp;10.&nbsp;Lifecycle Events"><div class="titlepage"><div><div><h2 class="title"><a name="lifecycle-events"></a>Chapter&nbsp;10.&nbsp;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&nbsp;10.1.&nbsp;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&lt;<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">int</span>[]&gt; 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&lt;<span xmlns="http://www.w3.org/1999/xhtml" class="hl-keyword">int</span>[]&gt;();
}
<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>&nbsp;</td><td width="20%" align="center"><a accesskey="u" href="cayenne-guide-part2.html">Up</a></td><td width="40%" align="right">&nbsp;<a accesskey="n" href="performance-tuning.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">Chapter&nbsp;9.&nbsp;Queries&nbsp;</td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top">&nbsp;Chapter&nbsp;11.&nbsp;Performance Tuning</td></tr></table></div></body></html>