You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@cayenne.apache.org by Daniel Scheibe <ds...@googlemail.com> on 2013/03/05 16:43:13 UTC

Lifecycle Listeners / cayenne-3.1B1

All,

i'm trying to get the lifecycle listeners working for my use case and i've
come accross a problem. I registered a listener to do some extra stuff for
an entity whenever it will be persisted (prePersist) via:

callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST, Content.class,
"prePersist");

This get's called as expected and works smoothly.

Now whenever i have the scenario of a CommitException thrown during
commitChanges() (for whatever reason, underlying database not available,
etc.) i need to revert some of the stuff i did in the "prePersist"
lifefycle callback on the object in question.

Unfortunately i haven't had luck yet to register a lifecycle listener that
will be called in case of a "rollback" through "rollbackChanges".

The documentation states something about "PostLoad" being called "Within
"ObjectContext.rollbackChanges()" after the object is reverted." (although
this is from 3.0 i guess it should still apply?
https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)

Is there any chance to get a kind of a "postRollback" lifecycle callback
working or something similar? Or did i just hit a bug with the version i'm
using?

Any help is much appreciated.

Cheers,
Daniel

Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Andrus Adamchik <an...@objectstyle.org>.
Having said that, maybe you can use this behavior to your advantage. E.g. store a hash of the input stream contents in the DB to be able to detect inconsistencies or something like that? 

Andrus


On Mar 11, 2013, at 10:07 AM, Andrus Adamchik <an...@objectstyle.org> wrote:
> I don't think there is a better way. Since Cayenne is only concerned with the state of object persistent properties, modifying any such property is the only way to to get callbacks. "Phantom" modifications (o.setX(o.getX()) or manually changing "persistenceState" are not going to cause lifecycle events.
> 
> Andrus
> 
> On Mar 8, 2013, at 12:56 PM, Daniel Scheibe <ds...@googlemail.com> wrote:
>> Okay, found a workaround for now. I added another field "modificationId"
>> which i modify on demand. This way the entity is really modified and
>> presented in the callback. But i think there must be a better way...
>> 
>> Cheers,
>> Daniel
>> 
>> 
>> 2013/3/7 Daniel Scheibe <ds...@googlemail.com>
>> 
>>> Andrus,
>>> 
>>> i'm making good progress but i have another quick question regarding the
>>> persistency state of an entity instance. When i set a property on my
>>> Content.class (inputStream) that is not a *real* field in (i.e.: mapped via
>>> cayenne) can i somehow flag this entity instance as modified? For instance,
>>> is it safe to mark the object as PersistenceState.MODIFIED from within when
>>> i know there has been a change or is this causing strange side effects?
>>> Otherwise it won't get recognized as a changed objects and i think the
>>> "prePersist" lifecycle callback won't get called too...
>>> 
>>> Thanks in advance!
>>> 
>>> Cheers,
>>> Daniel
>>> 
>>> 
>>> 2013/3/7 Daniel Scheibe <ds...@googlemail.com>
>>> 
>>>> Thanks Andrus,
>>>> 
>>>> i will do a prototype based on your suggestions (looks promising) to see
>>>> if that works for me. I agree on not putting too much logic into the
>>>> Persistent Objects. I just need to expose all of the functionality combined
>>>> through a common interface on top of the actual cayenne/filestore
>>>> implementation as the user of this shouldn't care about how the data is
>>>> stored underneath the surface.
>>>> 
>>>> Basically it should be layered like this:
>>>> 
>>>> Persistency Layer (combines and abstract all the functionality underneath)
>>>>   - Cayenne Layer
>>>>   - Filestore Layer
>>>> 
>>>> Cheers,
>>>> Daniel
>>>> 
>>>> 
>>>> 
>>>> 2013/3/7 Andrus Adamchik <an...@objectstyle.org>
>>>> 
>>>>> Understood the scenario.
>>>>> 
>>>>> I usually stay away from too much logic like that in the objects
>>>>> themselves and put that in some external handler classes, that can be
>>>>> registered as listeners if needed. It is much easier (and cleaner) to
>>>>> access application-specific "context" inside listeners that are not
>>>>> persistent objects. A good example can be found in the "Combining Listeners
>>>>> with DataChannelFilters" docs:
>>>>> 
>>>>> 
>>>>> http://cayenne.apache.org/docs/3.1/cayenne-guide/lifecycle-events.html#comining-listeners-with-datachannelfilters
>>>>> 
>>>>> In your case you may also implement a DataChannelFilter that does stream
>>>>> processing. E.g.:
>>>>> 
>>>>> public class StreamHandler implements DataChannelFilter {
>>>>> 
>>>>>   private ThreadLocal<Collection<InputStream>> streams;
>>>>> 
>>>>> 
>>>>>   // this method collects streams that need to be processed in the
>>>>> current transaction
>>>>>   @PrePersist(entities = Content.class)
>>>>>   @PreUpdate(entities = Content.class)
>>>>>   @PreRemove(entities = Content.class)
>>>>>   void beforeCommit(Content object) {
>>>>>       streams.get().add(object.getStream());
>>>>>   }
>>>>> 
>>>>>   @Override
>>>>>   public void init(DataChannel channel) {
>>>>>       streams = new ThreadLocal<Collection<InputStream>>();
>>>>>   }
>>>>> 
>>>>>   @Override
>>>>>   public QueryResponse onQuery(ObjectContext originatingContext, Query
>>>>> query, DataChannelFilterChain filterChain) {
>>>>>       return filterChain.onQuery(originatingContext, query);
>>>>>   }
>>>>> 
>>>>>   @Override
>>>>>   public GraphDiff onSync(ObjectContext originatingContext, GraphDiff
>>>>> changes, int syncType,
>>>>>           DataChannelFilterChain filterChain) {
>>>>> 
>>>>>       // ignore all but commits
>>>>>       if(syncType != FLUSH_CASCADE_SYNC) {
>>>>>           return filterChain.onSync(originatingContext, changes,
>>>>> syncType);
>>>>>       }
>>>>> 
>>>>>       streams.set(new ArrayList<InputStream>());
>>>>> 
>>>>>       try {
>>>>>           GraphDiff result = filterChain.onSync(originatingContext,
>>>>> changes, syncType);
>>>>> 
>>>>>           // SUCCESS: handle streams
>>>>>           return result;
>>>>>       }
>>>>>       catch(CayenneRuntimeException e) {
>>>>>               // FAILURE: handle streams, rollback the context
>>>>>               ...
>>>>>               originatingContext.rollbackChanges();
>>>>>       }
>>>>>       finally {
>>>>>           streams.set(null);
>>>>>       }
>>>>>   }
>>>>> 
>>>>> }
>>>>> 
>>>>> 
>>>>> On Mar 6, 2013, at 6:51 PM, Daniel Scheibe <ds...@googlemail.com>
>>>>> wrote:
>>>>>> Thanks for the quick response, Andrus!
>>>>>> 
>>>>>> I just came across the lines you just posted in "ObjectStore / void
>>>>>> objectsRolledBack()" and i guess i'm starting to understand the
>>>>> process a
>>>>>> bit better now. I also agree that changing the behaviour of unsetting
>>>>>> "objectContext" is not an option here.
>>>>>> 
>>>>>> Let me describe my use case a bit below:
>>>>>> 
>>>>>> I basically have a "Content" entity that reflects a File including the
>>>>> name
>>>>>> and the binary content. Now i have an implementation similar to this:
>>>>>> 
>>>>>> public class Content extends _Content
>>>>>> {
>>>>>>  private InputStream _inputStream = null;
>>>>>> 
>>>>>>  @Override
>>>>>>  public void setData(String name, InputStream inputStream)
>>>>>>  {
>>>>>>      setName(name);
>>>>>> 
>>>>>>      _inputStream = inputStream;
>>>>>>  }
>>>>>> 
>>>>>>  /* ... */
>>>>>> }
>>>>>> 
>>>>>> Now i have the following scenarios below:
>>>>>> 
>>>>>> 1. commitChanges for "Content" entity instance in persistence state
>>>>> "NEW"
>>>>>>  - If an input stream was set, need to write the input stream to my
>>>>> Http
>>>>>> filestore in "prePersist"
>>>>>> 
>>>>>> 2. commitChanges fails and rollbackChanges is called for "Content"
>>>>> entity
>>>>>> instance in persistence state "NEW"
>>>>>>  - If an input stream was written to the filestore in 1. i need to
>>>>>> remove the previously written input stream from my Http filestore in
>>>>>> "postRollback" (This is what i'm struggling with...)
>>>>>> 
>>>>>> 3. commitChanges for "Content" entity instance in persistence state
>>>>>> "MODIFIED"
>>>>>>  - If an input stream is already linked to this entity instance,
>>>>>> remember it in "prePersist"
>>>>>>  - If a new input stream was set, write the input stream to my Http
>>>>>> filestore in "prePersist"
>>>>>>  - Remove the previously remembered file input stream from my Http
>>>>>> filestore in "postCommit"
>>>>>> 
>>>>>> 4. commitChanges for "Content" entity instance in persistence state
>>>>>> "DELETED"
>>>>>>  - If there is an input stream linked to this instance, remove it
>>>>> from
>>>>>> my Http filestore in "postCommit" or "postDelete"
>>>>>> 
>>>>>> Furthermore I store what i call "services" as user properties in the
>>>>> object
>>>>>> context instance via ObjectContext.setUserProperty("...") to access
>>>>> these
>>>>>> in my callbacks later on, for example the client wrapper to my Http
>>>>>> filestore. Now in every callback method i grab the filestore client
>>>>>> instance via ObjectContext.getUserProperty("...") to access it.
>>>>>> 
>>>>>> What i currently achieved so far is to integrate a callback into my
>>>>>> application to let me know whenever an "Content" entity instance is in
>>>>>> persistence state "NEW" and get's rolled back (scenario described in
>>>>> 1. by
>>>>>> implementing another workaround-lifecycle listener for "postRollback".
>>>>>> 
>>>>>> Unfortunately when this is called the object is in the persistence
>>>>> state
>>>>>> TRANSIENT and i no longer have access to the object context as it was
>>>>> unset
>>>>>> before.
>>>>>> 
>>>>>> So what i'm trying to achieve is to fully integrate the storage of a
>>>>> binary
>>>>>> file maintaining all aspects (lifecycle) of the "Content" entity and
>>>>>> therefore need to make sure that whenever a commit was successfully
>>>>>> executed the related file content was stored in the Http filestore. If
>>>>>> something goes wrong somewhere in the middle (commitChanges() throws an
>>>>>> exception and i call rollbackChanges()) i try to undo/remove the file
>>>>> from
>>>>>> the Http filestore again if possible and only leaving back an orphaned
>>>>>> (unlinked) file at worst.
>>>>>> 
>>>>>> I'm not worried about getting my "postRollback" workaround done but
>>>>> maybe
>>>>>> there is a better option to "inject" services into entities (not using
>>>>> the
>>>>>> ObjectContext as a workaround) or something?
>>>>>> 
>>>>>> I hope you get the idea here :)
>>>>>> 
>>>>>> Thanks in advance!
>>>>>> 
>>>>>> Cheers,
>>>>>> Daniel
>>>>>> 
>>>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>>>>> 
>>>>>>> You are correct. The code that does it goes like this:
>>>>>>> 
>>>>>>> object.setObjectContext(null);
>>>>>>> object.setObjectId(null);
>>>>>>> object.setPersistenceState(PersistenceState.TRANSIENT);
>>>>>>> 
>>>>>>> I've been thinking about it recently. Rollback by definition wipes
>>>>> out all
>>>>>>> the uncommitted changes to the ObjectContext and its objects. I think
>>>>> we
>>>>>>> might preserve ObjectId property. Keeping it around does no harm, even
>>>>>>> though technically it is a side effect of the object previously being
>>>>>>> registered with the context. We might change this behavior.
>>>>>>> 
>>>>>>> I am more hesitant to change the behavior unsetting "objectContext"
>>>>>>> property. It appears to be a more consequential side effect.
>>>>>>> 
>>>>>>> I guess we just need an explicit rollback callback invoked prior to
>>>>>>> kicking the object out. I'll put it on the TODO list for 3.2. In the
>>>>>>> meantime I guess you'll have to implement some workaround. If you
>>>>> describe
>>>>>>> what your app does, we can think of a way to catch this.
>>>>>>> 
>>>>>>> Cheers,
>>>>>>> Andrus
>>>>>>> 
>>>>>>> 
>>>>>>> On Mar 6, 2013, at 4:58 PM, Daniel Scheibe <dscheibe79@googlemail.com
>>>>>> 
>>>>>>> wrote:
>>>>>>>> Hi Andrus,
>>>>>>>> 
>>>>>>>> Exactly, i'm dealing with NEW objects. While trying to build a
>>>>> workaround
>>>>>>>> for a "postRollback" callback i also noticed that the objects are in
>>>>> a
>>>>>>>> TRANSIENT state after rollbackChanges() was executed. Unfortunately
>>>>>>> objects
>>>>>>>> in this state don't contain a lot of useful data anymore (ObjectId,
>>>>>>>> ObjectContext is gone).
>>>>>>>> 
>>>>>>>> Cheers,
>>>>>>>> Daniel
>>>>>>>> 
>>>>>>>> 
>>>>>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>>>>>>> 
>>>>>>>>> Haven't tried to run it yet, but rolling back NEW object transfers
>>>>> them
>>>>>>>>> into the TRANSIENT state. For this state change postLoad (or any
>>>>> other
>>>>>>>>> callback) is indeed not invoked. It will be called for rolled back
>>>>>>> MODIFIED
>>>>>>>>> and DELETED objects.
>>>>>>>>> 
>>>>>>>>> So in your application, are you dealing with reverting NEW objects
>>>>> too?
>>>>>>>>> 
>>>>>>>>> Andrus
>>>>>>>>> 
>>>>>>>>> On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <
>>>>> dscheibe79@googlemail.com>
>>>>>>>>> wrote:
>>>>>>>>> 
>>>>>>>>>> Hi Andrus,
>>>>>>>>>> 
>>>>>>>>>> thanks for your feedback. I tried to dig into the Cayenne source
>>>>> code
>>>>>>>>> from
>>>>>>>>>> the trunk to see if there actually already is a test case for my
>>>>>>>>> scenario.
>>>>>>>>>> Unfortunately i wasn't able to find one so i tried to build one
>>>>> myself
>>>>>>>>>> (should make it fairly easy to test). Please find my test case
>>>>> below (I
>>>>>>>>>> hope it is correct as i'm not yet familiar with the Cayenne source
>>>>> code
>>>>>>>>>> structure)
>>>>>>>>>> 
>>>>>>>>>> So here is the code (i implemented it in
>>>>>>>>>> org.apache.cayenne.access.DataContextCallbacksTest):
>>>>>>>>>> 
>>>>>>>>>> public void testPostLoadCallbacks() {
>>>>>>>>>> 
>>>>>>>>>>    LifecycleCallbackRegistry registry = runtime
>>>>>>>>>>            .getDataDomain()
>>>>>>>>>>            .getEntityResolver()
>>>>>>>>>>            .getCallbackRegistry();
>>>>>>>>>> 
>>>>>>>>>>    // no callbacks
>>>>>>>>>>    Artist a1 = context.newObject(Artist.class);
>>>>>>>>>>    assertTrue(a1.getPostLoaded() == 0);
>>>>>>>>>> 
>>>>>>>>>>    try {
>>>>>>>>>>        context.commitChanges();
>>>>>>>>>>    } catch (CayenneRuntimeException cre) {
>>>>>>>>>>        context.rollbackChanges();
>>>>>>>>>>        assertTrue(a1.getPostLoaded() == 0);
>>>>>>>>>>    }
>>>>>>>>>> 
>>>>>>>>>>    registry
>>>>>>>>>>            .addListener(LifecycleEvent.POST_LOAD, Artist.class,
>>>>>>>>>> "postLoadCallback");
>>>>>>>>>> 
>>>>>>>>>>    Artist a2 = context.newObject(Artist.class);
>>>>>>>>>>    assertTrue(a2.getPostLoaded() == 0);
>>>>>>>>>> 
>>>>>>>>>>    try {
>>>>>>>>>>        context.commitChanges();
>>>>>>>>>>    } catch (CayenneRuntimeException cre) {
>>>>>>>>>>        context.rollbackChanges();
>>>>>>>>>>        assertTrue(a2.getPostLoaded() > 0);
>>>>>>>>>>    }
>>>>>>>>>> }
>>>>>>>>>> 
>>>>>>>>>> Should this test pass successfully or did i do something wrong
>>>>> here (I
>>>>>>>>>> assume a2.getPostLoaded() should return a non-zero value after a
>>>>>>>>> rollback)?
>>>>>>>>>> 
>>>>>>>>>> Thanks in advance!
>>>>>>>>>> 
>>>>>>>>>> Cheers,
>>>>>>>>>> Daniel
>>>>>>>>>> 
>>>>>>>>>> 
>>>>>>>>>> 
>>>>>>>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>>>>>>>>> 
>>>>>>>>>>> Hi Daniel,
>>>>>>>>>>> 
>>>>>>>>>>> Yes, post load callback should be invoked as advertised. I never
>>>>>>>>>>> personally tried it from a commit catch block, but it should
>>>>> work. Do
>>>>>>>>> you
>>>>>>>>>>> have a code sample? Maybe there is a scenario that we do not
>>>>> handle.
>>>>>>>>>>> 
>>>>>>>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>>>>>>>>> callback
>>>>>>>>>>>> working or something similar?
>>>>>>>>>>> 
>>>>>>>>>>> The original callbacks were taken from the JPA spec that doesn't
>>>>>>> specify
>>>>>>>>>>> postRollback. We've already diverged from JPA by adding PostAdd. I
>>>>>>>>> think we
>>>>>>>>>>> might go further to better reflect Cayenne object lifecycle. So I
>>>>> am
>>>>>>>>> open
>>>>>>>>>>> to adding PostRollback in the future (need to think it through
>>>>>>> though)…
>>>>>>>>>>> 
>>>>>>>>>>> Andrus
>>>>>>>>>>> 
>>>>>>>>>>> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <
>>>>> dscheibe79@googlemail.com
>>>>>>>> 
>>>>>>>>>>> wrote:
>>>>>>>>>>>> All,
>>>>>>>>>>>> 
>>>>>>>>>>>> i'm trying to get the lifecycle listeners working for my use
>>>>> case and
>>>>>>>>>>> i've
>>>>>>>>>>>> come accross a problem. I registered a listener to do some extra
>>>>>>> stuff
>>>>>>>>>>> for
>>>>>>>>>>>> an entity whenever it will be persisted (prePersist) via:
>>>>>>>>>>>> 
>>>>>>>>>>>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST,
>>>>>>> Content.class,
>>>>>>>>>>>> "prePersist");
>>>>>>>>>>>> 
>>>>>>>>>>>> This get's called as expected and works smoothly.
>>>>>>>>>>>> 
>>>>>>>>>>>> Now whenever i have the scenario of a CommitException thrown
>>>>> during
>>>>>>>>>>>> commitChanges() (for whatever reason, underlying database not
>>>>>>>>> available,
>>>>>>>>>>>> etc.) i need to revert some of the stuff i did in the
>>>>> "prePersist"
>>>>>>>>>>>> lifefycle callback on the object in question.
>>>>>>>>>>>> 
>>>>>>>>>>>> Unfortunately i haven't had luck yet to register a lifecycle
>>>>> listener
>>>>>>>>>>> that
>>>>>>>>>>>> will be called in case of a "rollback" through "rollbackChanges".
>>>>>>>>>>>> 
>>>>>>>>>>>> The documentation states something about "PostLoad" being called
>>>>>>>>> "Within
>>>>>>>>>>>> "ObjectContext.rollbackChanges()" after the object is reverted."
>>>>>>>>>>> (although
>>>>>>>>>>>> this is from 3.0 i guess it should still apply?
>>>>>>>>>>>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
>>>>>>>>>>>> 
>>>>>>>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>>>>>>>>> callback
>>>>>>>>>>>> working or something similar? Or did i just hit a bug with the
>>>>>>> version
>>>>>>>>>>> i'm
>>>>>>>>>>>> using?
>>>>>>>>>>>> 
>>>>>>>>>>>> Any help is much appreciated.
>>>>>>>>>>>> 
>>>>>>>>>>>> Cheers,
>>>>>>>>>>>> Daniel
>>>>>>>>>>> 
>>>>>>>>>>> 
>>>>>>>>> 
>>>>>>>>> 
>>>>>>> 
>>>>>>> 
>>>>> 
>>>>> 
>>>> 
>>> 
> 
> 


Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Andrus Adamchik <an...@objectstyle.org>.
I don't think there is a better way. Since Cayenne is only concerned with the state of object persistent properties, modifying any such property is the only way to to get callbacks. "Phantom" modifications (o.setX(o.getX()) or manually changing "persistenceState" are not going to cause lifecycle events.

Andrus

On Mar 8, 2013, at 12:56 PM, Daniel Scheibe <ds...@googlemail.com> wrote:
> Okay, found a workaround for now. I added another field "modificationId"
> which i modify on demand. This way the entity is really modified and
> presented in the callback. But i think there must be a better way...
> 
> Cheers,
> Daniel
> 
> 
> 2013/3/7 Daniel Scheibe <ds...@googlemail.com>
> 
>> Andrus,
>> 
>> i'm making good progress but i have another quick question regarding the
>> persistency state of an entity instance. When i set a property on my
>> Content.class (inputStream) that is not a *real* field in (i.e.: mapped via
>> cayenne) can i somehow flag this entity instance as modified? For instance,
>> is it safe to mark the object as PersistenceState.MODIFIED from within when
>> i know there has been a change or is this causing strange side effects?
>> Otherwise it won't get recognized as a changed objects and i think the
>> "prePersist" lifecycle callback won't get called too...
>> 
>> Thanks in advance!
>> 
>> Cheers,
>> Daniel
>> 
>> 
>> 2013/3/7 Daniel Scheibe <ds...@googlemail.com>
>> 
>>> Thanks Andrus,
>>> 
>>> i will do a prototype based on your suggestions (looks promising) to see
>>> if that works for me. I agree on not putting too much logic into the
>>> Persistent Objects. I just need to expose all of the functionality combined
>>> through a common interface on top of the actual cayenne/filestore
>>> implementation as the user of this shouldn't care about how the data is
>>> stored underneath the surface.
>>> 
>>> Basically it should be layered like this:
>>> 
>>> Persistency Layer (combines and abstract all the functionality underneath)
>>>    - Cayenne Layer
>>>    - Filestore Layer
>>> 
>>> Cheers,
>>> Daniel
>>> 
>>> 
>>> 
>>> 2013/3/7 Andrus Adamchik <an...@objectstyle.org>
>>> 
>>>> Understood the scenario.
>>>> 
>>>> I usually stay away from too much logic like that in the objects
>>>> themselves and put that in some external handler classes, that can be
>>>> registered as listeners if needed. It is much easier (and cleaner) to
>>>> access application-specific "context" inside listeners that are not
>>>> persistent objects. A good example can be found in the "Combining Listeners
>>>> with DataChannelFilters" docs:
>>>> 
>>>> 
>>>> http://cayenne.apache.org/docs/3.1/cayenne-guide/lifecycle-events.html#comining-listeners-with-datachannelfilters
>>>> 
>>>> In your case you may also implement a DataChannelFilter that does stream
>>>> processing. E.g.:
>>>> 
>>>> public class StreamHandler implements DataChannelFilter {
>>>> 
>>>>    private ThreadLocal<Collection<InputStream>> streams;
>>>> 
>>>> 
>>>>    // this method collects streams that need to be processed in the
>>>> current transaction
>>>>    @PrePersist(entities = Content.class)
>>>>    @PreUpdate(entities = Content.class)
>>>>    @PreRemove(entities = Content.class)
>>>>    void beforeCommit(Content object) {
>>>>        streams.get().add(object.getStream());
>>>>    }
>>>> 
>>>>    @Override
>>>>    public void init(DataChannel channel) {
>>>>        streams = new ThreadLocal<Collection<InputStream>>();
>>>>    }
>>>> 
>>>>    @Override
>>>>    public QueryResponse onQuery(ObjectContext originatingContext, Query
>>>> query, DataChannelFilterChain filterChain) {
>>>>        return filterChain.onQuery(originatingContext, query);
>>>>    }
>>>> 
>>>>    @Override
>>>>    public GraphDiff onSync(ObjectContext originatingContext, GraphDiff
>>>> changes, int syncType,
>>>>            DataChannelFilterChain filterChain) {
>>>> 
>>>>        // ignore all but commits
>>>>        if(syncType != FLUSH_CASCADE_SYNC) {
>>>>            return filterChain.onSync(originatingContext, changes,
>>>> syncType);
>>>>        }
>>>> 
>>>>        streams.set(new ArrayList<InputStream>());
>>>> 
>>>>        try {
>>>>            GraphDiff result = filterChain.onSync(originatingContext,
>>>> changes, syncType);
>>>> 
>>>>            // SUCCESS: handle streams
>>>>            return result;
>>>>        }
>>>>        catch(CayenneRuntimeException e) {
>>>>                // FAILURE: handle streams, rollback the context
>>>>                ...
>>>>                originatingContext.rollbackChanges();
>>>>        }
>>>>        finally {
>>>>            streams.set(null);
>>>>        }
>>>>    }
>>>> 
>>>> }
>>>> 
>>>> 
>>>> On Mar 6, 2013, at 6:51 PM, Daniel Scheibe <ds...@googlemail.com>
>>>> wrote:
>>>>> Thanks for the quick response, Andrus!
>>>>> 
>>>>> I just came across the lines you just posted in "ObjectStore / void
>>>>> objectsRolledBack()" and i guess i'm starting to understand the
>>>> process a
>>>>> bit better now. I also agree that changing the behaviour of unsetting
>>>>> "objectContext" is not an option here.
>>>>> 
>>>>> Let me describe my use case a bit below:
>>>>> 
>>>>> I basically have a "Content" entity that reflects a File including the
>>>> name
>>>>> and the binary content. Now i have an implementation similar to this:
>>>>> 
>>>>> public class Content extends _Content
>>>>> {
>>>>>   private InputStream _inputStream = null;
>>>>> 
>>>>>   @Override
>>>>>   public void setData(String name, InputStream inputStream)
>>>>>   {
>>>>>       setName(name);
>>>>> 
>>>>>       _inputStream = inputStream;
>>>>>   }
>>>>> 
>>>>>   /* ... */
>>>>> }
>>>>> 
>>>>> Now i have the following scenarios below:
>>>>> 
>>>>> 1. commitChanges for "Content" entity instance in persistence state
>>>> "NEW"
>>>>>   - If an input stream was set, need to write the input stream to my
>>>> Http
>>>>> filestore in "prePersist"
>>>>> 
>>>>> 2. commitChanges fails and rollbackChanges is called for "Content"
>>>> entity
>>>>> instance in persistence state "NEW"
>>>>>   - If an input stream was written to the filestore in 1. i need to
>>>>> remove the previously written input stream from my Http filestore in
>>>>> "postRollback" (This is what i'm struggling with...)
>>>>> 
>>>>> 3. commitChanges for "Content" entity instance in persistence state
>>>>> "MODIFIED"
>>>>>   - If an input stream is already linked to this entity instance,
>>>>> remember it in "prePersist"
>>>>>   - If a new input stream was set, write the input stream to my Http
>>>>> filestore in "prePersist"
>>>>>   - Remove the previously remembered file input stream from my Http
>>>>> filestore in "postCommit"
>>>>> 
>>>>> 4. commitChanges for "Content" entity instance in persistence state
>>>>> "DELETED"
>>>>>   - If there is an input stream linked to this instance, remove it
>>>> from
>>>>> my Http filestore in "postCommit" or "postDelete"
>>>>> 
>>>>> Furthermore I store what i call "services" as user properties in the
>>>> object
>>>>> context instance via ObjectContext.setUserProperty("...") to access
>>>> these
>>>>> in my callbacks later on, for example the client wrapper to my Http
>>>>> filestore. Now in every callback method i grab the filestore client
>>>>> instance via ObjectContext.getUserProperty("...") to access it.
>>>>> 
>>>>> What i currently achieved so far is to integrate a callback into my
>>>>> application to let me know whenever an "Content" entity instance is in
>>>>> persistence state "NEW" and get's rolled back (scenario described in
>>>> 1. by
>>>>> implementing another workaround-lifecycle listener for "postRollback".
>>>>> 
>>>>> Unfortunately when this is called the object is in the persistence
>>>> state
>>>>> TRANSIENT and i no longer have access to the object context as it was
>>>> unset
>>>>> before.
>>>>> 
>>>>> So what i'm trying to achieve is to fully integrate the storage of a
>>>> binary
>>>>> file maintaining all aspects (lifecycle) of the "Content" entity and
>>>>> therefore need to make sure that whenever a commit was successfully
>>>>> executed the related file content was stored in the Http filestore. If
>>>>> something goes wrong somewhere in the middle (commitChanges() throws an
>>>>> exception and i call rollbackChanges()) i try to undo/remove the file
>>>> from
>>>>> the Http filestore again if possible and only leaving back an orphaned
>>>>> (unlinked) file at worst.
>>>>> 
>>>>> I'm not worried about getting my "postRollback" workaround done but
>>>> maybe
>>>>> there is a better option to "inject" services into entities (not using
>>>> the
>>>>> ObjectContext as a workaround) or something?
>>>>> 
>>>>> I hope you get the idea here :)
>>>>> 
>>>>> Thanks in advance!
>>>>> 
>>>>> Cheers,
>>>>> Daniel
>>>>> 
>>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>>>> 
>>>>>> You are correct. The code that does it goes like this:
>>>>>> 
>>>>>> object.setObjectContext(null);
>>>>>> object.setObjectId(null);
>>>>>> object.setPersistenceState(PersistenceState.TRANSIENT);
>>>>>> 
>>>>>> I've been thinking about it recently. Rollback by definition wipes
>>>> out all
>>>>>> the uncommitted changes to the ObjectContext and its objects. I think
>>>> we
>>>>>> might preserve ObjectId property. Keeping it around does no harm, even
>>>>>> though technically it is a side effect of the object previously being
>>>>>> registered with the context. We might change this behavior.
>>>>>> 
>>>>>> I am more hesitant to change the behavior unsetting "objectContext"
>>>>>> property. It appears to be a more consequential side effect.
>>>>>> 
>>>>>> I guess we just need an explicit rollback callback invoked prior to
>>>>>> kicking the object out. I'll put it on the TODO list for 3.2. In the
>>>>>> meantime I guess you'll have to implement some workaround. If you
>>>> describe
>>>>>> what your app does, we can think of a way to catch this.
>>>>>> 
>>>>>> Cheers,
>>>>>> Andrus
>>>>>> 
>>>>>> 
>>>>>> On Mar 6, 2013, at 4:58 PM, Daniel Scheibe <dscheibe79@googlemail.com
>>>>> 
>>>>>> wrote:
>>>>>>> Hi Andrus,
>>>>>>> 
>>>>>>> Exactly, i'm dealing with NEW objects. While trying to build a
>>>> workaround
>>>>>>> for a "postRollback" callback i also noticed that the objects are in
>>>> a
>>>>>>> TRANSIENT state after rollbackChanges() was executed. Unfortunately
>>>>>> objects
>>>>>>> in this state don't contain a lot of useful data anymore (ObjectId,
>>>>>>> ObjectContext is gone).
>>>>>>> 
>>>>>>> Cheers,
>>>>>>> Daniel
>>>>>>> 
>>>>>>> 
>>>>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>>>>>> 
>>>>>>>> Haven't tried to run it yet, but rolling back NEW object transfers
>>>> them
>>>>>>>> into the TRANSIENT state. For this state change postLoad (or any
>>>> other
>>>>>>>> callback) is indeed not invoked. It will be called for rolled back
>>>>>> MODIFIED
>>>>>>>> and DELETED objects.
>>>>>>>> 
>>>>>>>> So in your application, are you dealing with reverting NEW objects
>>>> too?
>>>>>>>> 
>>>>>>>> Andrus
>>>>>>>> 
>>>>>>>> On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <
>>>> dscheibe79@googlemail.com>
>>>>>>>> wrote:
>>>>>>>> 
>>>>>>>>> Hi Andrus,
>>>>>>>>> 
>>>>>>>>> thanks for your feedback. I tried to dig into the Cayenne source
>>>> code
>>>>>>>> from
>>>>>>>>> the trunk to see if there actually already is a test case for my
>>>>>>>> scenario.
>>>>>>>>> Unfortunately i wasn't able to find one so i tried to build one
>>>> myself
>>>>>>>>> (should make it fairly easy to test). Please find my test case
>>>> below (I
>>>>>>>>> hope it is correct as i'm not yet familiar with the Cayenne source
>>>> code
>>>>>>>>> structure)
>>>>>>>>> 
>>>>>>>>> So here is the code (i implemented it in
>>>>>>>>> org.apache.cayenne.access.DataContextCallbacksTest):
>>>>>>>>> 
>>>>>>>>> public void testPostLoadCallbacks() {
>>>>>>>>> 
>>>>>>>>>     LifecycleCallbackRegistry registry = runtime
>>>>>>>>>             .getDataDomain()
>>>>>>>>>             .getEntityResolver()
>>>>>>>>>             .getCallbackRegistry();
>>>>>>>>> 
>>>>>>>>>     // no callbacks
>>>>>>>>>     Artist a1 = context.newObject(Artist.class);
>>>>>>>>>     assertTrue(a1.getPostLoaded() == 0);
>>>>>>>>> 
>>>>>>>>>     try {
>>>>>>>>>         context.commitChanges();
>>>>>>>>>     } catch (CayenneRuntimeException cre) {
>>>>>>>>>         context.rollbackChanges();
>>>>>>>>>         assertTrue(a1.getPostLoaded() == 0);
>>>>>>>>>     }
>>>>>>>>> 
>>>>>>>>>     registry
>>>>>>>>>             .addListener(LifecycleEvent.POST_LOAD, Artist.class,
>>>>>>>>> "postLoadCallback");
>>>>>>>>> 
>>>>>>>>>     Artist a2 = context.newObject(Artist.class);
>>>>>>>>>     assertTrue(a2.getPostLoaded() == 0);
>>>>>>>>> 
>>>>>>>>>     try {
>>>>>>>>>         context.commitChanges();
>>>>>>>>>     } catch (CayenneRuntimeException cre) {
>>>>>>>>>         context.rollbackChanges();
>>>>>>>>>         assertTrue(a2.getPostLoaded() > 0);
>>>>>>>>>     }
>>>>>>>>> }
>>>>>>>>> 
>>>>>>>>> Should this test pass successfully or did i do something wrong
>>>> here (I
>>>>>>>>> assume a2.getPostLoaded() should return a non-zero value after a
>>>>>>>> rollback)?
>>>>>>>>> 
>>>>>>>>> Thanks in advance!
>>>>>>>>> 
>>>>>>>>> Cheers,
>>>>>>>>> Daniel
>>>>>>>>> 
>>>>>>>>> 
>>>>>>>>> 
>>>>>>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>>>>>>>> 
>>>>>>>>>> Hi Daniel,
>>>>>>>>>> 
>>>>>>>>>> Yes, post load callback should be invoked as advertised. I never
>>>>>>>>>> personally tried it from a commit catch block, but it should
>>>> work. Do
>>>>>>>> you
>>>>>>>>>> have a code sample? Maybe there is a scenario that we do not
>>>> handle.
>>>>>>>>>> 
>>>>>>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>>>>>>>> callback
>>>>>>>>>>> working or something similar?
>>>>>>>>>> 
>>>>>>>>>> The original callbacks were taken from the JPA spec that doesn't
>>>>>> specify
>>>>>>>>>> postRollback. We've already diverged from JPA by adding PostAdd. I
>>>>>>>> think we
>>>>>>>>>> might go further to better reflect Cayenne object lifecycle. So I
>>>> am
>>>>>>>> open
>>>>>>>>>> to adding PostRollback in the future (need to think it through
>>>>>> though)…
>>>>>>>>>> 
>>>>>>>>>> Andrus
>>>>>>>>>> 
>>>>>>>>>> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <
>>>> dscheibe79@googlemail.com
>>>>>>> 
>>>>>>>>>> wrote:
>>>>>>>>>>> All,
>>>>>>>>>>> 
>>>>>>>>>>> i'm trying to get the lifecycle listeners working for my use
>>>> case and
>>>>>>>>>> i've
>>>>>>>>>>> come accross a problem. I registered a listener to do some extra
>>>>>> stuff
>>>>>>>>>> for
>>>>>>>>>>> an entity whenever it will be persisted (prePersist) via:
>>>>>>>>>>> 
>>>>>>>>>>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST,
>>>>>> Content.class,
>>>>>>>>>>> "prePersist");
>>>>>>>>>>> 
>>>>>>>>>>> This get's called as expected and works smoothly.
>>>>>>>>>>> 
>>>>>>>>>>> Now whenever i have the scenario of a CommitException thrown
>>>> during
>>>>>>>>>>> commitChanges() (for whatever reason, underlying database not
>>>>>>>> available,
>>>>>>>>>>> etc.) i need to revert some of the stuff i did in the
>>>> "prePersist"
>>>>>>>>>>> lifefycle callback on the object in question.
>>>>>>>>>>> 
>>>>>>>>>>> Unfortunately i haven't had luck yet to register a lifecycle
>>>> listener
>>>>>>>>>> that
>>>>>>>>>>> will be called in case of a "rollback" through "rollbackChanges".
>>>>>>>>>>> 
>>>>>>>>>>> The documentation states something about "PostLoad" being called
>>>>>>>> "Within
>>>>>>>>>>> "ObjectContext.rollbackChanges()" after the object is reverted."
>>>>>>>>>> (although
>>>>>>>>>>> this is from 3.0 i guess it should still apply?
>>>>>>>>>>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
>>>>>>>>>>> 
>>>>>>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>>>>>>>> callback
>>>>>>>>>>> working or something similar? Or did i just hit a bug with the
>>>>>> version
>>>>>>>>>> i'm
>>>>>>>>>>> using?
>>>>>>>>>>> 
>>>>>>>>>>> Any help is much appreciated.
>>>>>>>>>>> 
>>>>>>>>>>> Cheers,
>>>>>>>>>>> Daniel
>>>>>>>>>> 
>>>>>>>>>> 
>>>>>>>> 
>>>>>>>> 
>>>>>> 
>>>>>> 
>>>> 
>>>> 
>>> 
>> 


Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Daniel Scheibe <ds...@googlemail.com>.
Okay, found a workaround for now. I added another field "modificationId"
which i modify on demand. This way the entity is really modified and
presented in the callback. But i think there must be a better way...

Cheers,
Daniel


2013/3/7 Daniel Scheibe <ds...@googlemail.com>

> Andrus,
>
> i'm making good progress but i have another quick question regarding the
> persistency state of an entity instance. When i set a property on my
> Content.class (inputStream) that is not a *real* field in (i.e.: mapped via
> cayenne) can i somehow flag this entity instance as modified? For instance,
> is it safe to mark the object as PersistenceState.MODIFIED from within when
> i know there has been a change or is this causing strange side effects?
> Otherwise it won't get recognized as a changed objects and i think the
> "prePersist" lifecycle callback won't get called too...
>
> Thanks in advance!
>
> Cheers,
> Daniel
>
>
> 2013/3/7 Daniel Scheibe <ds...@googlemail.com>
>
>> Thanks Andrus,
>>
>> i will do a prototype based on your suggestions (looks promising) to see
>> if that works for me. I agree on not putting too much logic into the
>> Persistent Objects. I just need to expose all of the functionality combined
>> through a common interface on top of the actual cayenne/filestore
>> implementation as the user of this shouldn't care about how the data is
>> stored underneath the surface.
>>
>> Basically it should be layered like this:
>>
>> Persistency Layer (combines and abstract all the functionality underneath)
>>     - Cayenne Layer
>>     - Filestore Layer
>>
>> Cheers,
>> Daniel
>>
>>
>>
>> 2013/3/7 Andrus Adamchik <an...@objectstyle.org>
>>
>>> Understood the scenario.
>>>
>>> I usually stay away from too much logic like that in the objects
>>> themselves and put that in some external handler classes, that can be
>>> registered as listeners if needed. It is much easier (and cleaner) to
>>> access application-specific "context" inside listeners that are not
>>> persistent objects. A good example can be found in the "Combining Listeners
>>> with DataChannelFilters" docs:
>>>
>>>
>>> http://cayenne.apache.org/docs/3.1/cayenne-guide/lifecycle-events.html#comining-listeners-with-datachannelfilters
>>>
>>> In your case you may also implement a DataChannelFilter that does stream
>>> processing. E.g.:
>>>
>>> public class StreamHandler implements DataChannelFilter {
>>>
>>>     private ThreadLocal<Collection<InputStream>> streams;
>>>
>>>
>>>     // this method collects streams that need to be processed in the
>>> current transaction
>>>     @PrePersist(entities = Content.class)
>>>     @PreUpdate(entities = Content.class)
>>>     @PreRemove(entities = Content.class)
>>>     void beforeCommit(Content object) {
>>>         streams.get().add(object.getStream());
>>>     }
>>>
>>>     @Override
>>>     public void init(DataChannel channel) {
>>>         streams = new ThreadLocal<Collection<InputStream>>();
>>>     }
>>>
>>>     @Override
>>>     public QueryResponse onQuery(ObjectContext originatingContext, Query
>>> query, DataChannelFilterChain filterChain) {
>>>         return filterChain.onQuery(originatingContext, query);
>>>     }
>>>
>>>     @Override
>>>     public GraphDiff onSync(ObjectContext originatingContext, GraphDiff
>>> changes, int syncType,
>>>             DataChannelFilterChain filterChain) {
>>>
>>>         // ignore all but commits
>>>         if(syncType != FLUSH_CASCADE_SYNC) {
>>>             return filterChain.onSync(originatingContext, changes,
>>> syncType);
>>>         }
>>>
>>>         streams.set(new ArrayList<InputStream>());
>>>
>>>         try {
>>>             GraphDiff result = filterChain.onSync(originatingContext,
>>> changes, syncType);
>>>
>>>             // SUCCESS: handle streams
>>>             return result;
>>>         }
>>>         catch(CayenneRuntimeException e) {
>>>                 // FAILURE: handle streams, rollback the context
>>>                 ...
>>>                 originatingContext.rollbackChanges();
>>>         }
>>>         finally {
>>>             streams.set(null);
>>>         }
>>>     }
>>>
>>> }
>>>
>>>
>>> On Mar 6, 2013, at 6:51 PM, Daniel Scheibe <ds...@googlemail.com>
>>> wrote:
>>> > Thanks for the quick response, Andrus!
>>> >
>>> > I just came across the lines you just posted in "ObjectStore / void
>>> > objectsRolledBack()" and i guess i'm starting to understand the
>>> process a
>>> > bit better now. I also agree that changing the behaviour of unsetting
>>> > "objectContext" is not an option here.
>>> >
>>> > Let me describe my use case a bit below:
>>> >
>>> > I basically have a "Content" entity that reflects a File including the
>>> name
>>> > and the binary content. Now i have an implementation similar to this:
>>> >
>>> > public class Content extends _Content
>>> > {
>>> >    private InputStream _inputStream = null;
>>> >
>>> >    @Override
>>> >    public void setData(String name, InputStream inputStream)
>>> >    {
>>> >        setName(name);
>>> >
>>> >        _inputStream = inputStream;
>>> >    }
>>> >
>>> >    /* ... */
>>> > }
>>> >
>>> > Now i have the following scenarios below:
>>> >
>>> > 1. commitChanges for "Content" entity instance in persistence state
>>> "NEW"
>>> >    - If an input stream was set, need to write the input stream to my
>>> Http
>>> > filestore in "prePersist"
>>> >
>>> > 2. commitChanges fails and rollbackChanges is called for "Content"
>>> entity
>>> > instance in persistence state "NEW"
>>> >    - If an input stream was written to the filestore in 1. i need to
>>> > remove the previously written input stream from my Http filestore in
>>> > "postRollback" (This is what i'm struggling with...)
>>> >
>>> > 3. commitChanges for "Content" entity instance in persistence state
>>> > "MODIFIED"
>>> >    - If an input stream is already linked to this entity instance,
>>> > remember it in "prePersist"
>>> >    - If a new input stream was set, write the input stream to my Http
>>> > filestore in "prePersist"
>>> >    - Remove the previously remembered file input stream from my Http
>>> > filestore in "postCommit"
>>> >
>>> > 4. commitChanges for "Content" entity instance in persistence state
>>> > "DELETED"
>>> >    - If there is an input stream linked to this instance, remove it
>>> from
>>> > my Http filestore in "postCommit" or "postDelete"
>>> >
>>> > Furthermore I store what i call "services" as user properties in the
>>> object
>>> > context instance via ObjectContext.setUserProperty("...") to access
>>> these
>>> > in my callbacks later on, for example the client wrapper to my Http
>>> > filestore. Now in every callback method i grab the filestore client
>>> > instance via ObjectContext.getUserProperty("...") to access it.
>>> >
>>> > What i currently achieved so far is to integrate a callback into my
>>> > application to let me know whenever an "Content" entity instance is in
>>> > persistence state "NEW" and get's rolled back (scenario described in
>>> 1. by
>>> > implementing another workaround-lifecycle listener for "postRollback".
>>> >
>>> > Unfortunately when this is called the object is in the persistence
>>> state
>>> > TRANSIENT and i no longer have access to the object context as it was
>>> unset
>>> > before.
>>> >
>>> > So what i'm trying to achieve is to fully integrate the storage of a
>>> binary
>>> > file maintaining all aspects (lifecycle) of the "Content" entity and
>>> > therefore need to make sure that whenever a commit was successfully
>>> > executed the related file content was stored in the Http filestore. If
>>> > something goes wrong somewhere in the middle (commitChanges() throws an
>>> > exception and i call rollbackChanges()) i try to undo/remove the file
>>> from
>>> > the Http filestore again if possible and only leaving back an orphaned
>>> > (unlinked) file at worst.
>>> >
>>> > I'm not worried about getting my "postRollback" workaround done but
>>> maybe
>>> > there is a better option to "inject" services into entities (not using
>>> the
>>> > ObjectContext as a workaround) or something?
>>> >
>>> > I hope you get the idea here :)
>>> >
>>> > Thanks in advance!
>>> >
>>> > Cheers,
>>> > Daniel
>>> >
>>> > 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>> >
>>> >> You are correct. The code that does it goes like this:
>>> >>
>>> >>  object.setObjectContext(null);
>>> >>  object.setObjectId(null);
>>> >>  object.setPersistenceState(PersistenceState.TRANSIENT);
>>> >>
>>> >> I've been thinking about it recently. Rollback by definition wipes
>>> out all
>>> >> the uncommitted changes to the ObjectContext and its objects. I think
>>> we
>>> >> might preserve ObjectId property. Keeping it around does no harm, even
>>> >> though technically it is a side effect of the object previously being
>>> >> registered with the context. We might change this behavior.
>>> >>
>>> >> I am more hesitant to change the behavior unsetting "objectContext"
>>> >> property. It appears to be a more consequential side effect.
>>> >>
>>> >> I guess we just need an explicit rollback callback invoked prior to
>>> >> kicking the object out. I'll put it on the TODO list for 3.2. In the
>>> >> meantime I guess you'll have to implement some workaround. If you
>>> describe
>>> >> what your app does, we can think of a way to catch this.
>>> >>
>>> >> Cheers,
>>> >> Andrus
>>> >>
>>> >>
>>> >> On Mar 6, 2013, at 4:58 PM, Daniel Scheibe <dscheibe79@googlemail.com
>>> >
>>> >> wrote:
>>> >>> Hi Andrus,
>>> >>>
>>> >>> Exactly, i'm dealing with NEW objects. While trying to build a
>>> workaround
>>> >>> for a "postRollback" callback i also noticed that the objects are in
>>> a
>>> >>> TRANSIENT state after rollbackChanges() was executed. Unfortunately
>>> >> objects
>>> >>> in this state don't contain a lot of useful data anymore (ObjectId,
>>> >>> ObjectContext is gone).
>>> >>>
>>> >>> Cheers,
>>> >>> Daniel
>>> >>>
>>> >>>
>>> >>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>> >>>
>>> >>>> Haven't tried to run it yet, but rolling back NEW object transfers
>>> them
>>> >>>> into the TRANSIENT state. For this state change postLoad (or any
>>> other
>>> >>>> callback) is indeed not invoked. It will be called for rolled back
>>> >> MODIFIED
>>> >>>> and DELETED objects.
>>> >>>>
>>> >>>> So in your application, are you dealing with reverting NEW objects
>>> too?
>>> >>>>
>>> >>>> Andrus
>>> >>>>
>>> >>>> On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <
>>> dscheibe79@googlemail.com>
>>> >>>> wrote:
>>> >>>>
>>> >>>>> Hi Andrus,
>>> >>>>>
>>> >>>>> thanks for your feedback. I tried to dig into the Cayenne source
>>> code
>>> >>>> from
>>> >>>>> the trunk to see if there actually already is a test case for my
>>> >>>> scenario.
>>> >>>>> Unfortunately i wasn't able to find one so i tried to build one
>>> myself
>>> >>>>> (should make it fairly easy to test). Please find my test case
>>> below (I
>>> >>>>> hope it is correct as i'm not yet familiar with the Cayenne source
>>> code
>>> >>>>> structure)
>>> >>>>>
>>> >>>>> So here is the code (i implemented it in
>>> >>>>> org.apache.cayenne.access.DataContextCallbacksTest):
>>> >>>>>
>>> >>>>>  public void testPostLoadCallbacks() {
>>> >>>>>
>>> >>>>>      LifecycleCallbackRegistry registry = runtime
>>> >>>>>              .getDataDomain()
>>> >>>>>              .getEntityResolver()
>>> >>>>>              .getCallbackRegistry();
>>> >>>>>
>>> >>>>>      // no callbacks
>>> >>>>>      Artist a1 = context.newObject(Artist.class);
>>> >>>>>      assertTrue(a1.getPostLoaded() == 0);
>>> >>>>>
>>> >>>>>      try {
>>> >>>>>          context.commitChanges();
>>> >>>>>      } catch (CayenneRuntimeException cre) {
>>> >>>>>          context.rollbackChanges();
>>> >>>>>          assertTrue(a1.getPostLoaded() == 0);
>>> >>>>>      }
>>> >>>>>
>>> >>>>>      registry
>>> >>>>>              .addListener(LifecycleEvent.POST_LOAD, Artist.class,
>>> >>>>> "postLoadCallback");
>>> >>>>>
>>> >>>>>      Artist a2 = context.newObject(Artist.class);
>>> >>>>>      assertTrue(a2.getPostLoaded() == 0);
>>> >>>>>
>>> >>>>>      try {
>>> >>>>>          context.commitChanges();
>>> >>>>>      } catch (CayenneRuntimeException cre) {
>>> >>>>>          context.rollbackChanges();
>>> >>>>>          assertTrue(a2.getPostLoaded() > 0);
>>> >>>>>      }
>>> >>>>>  }
>>> >>>>>
>>> >>>>> Should this test pass successfully or did i do something wrong
>>> here (I
>>> >>>>> assume a2.getPostLoaded() should return a non-zero value after a
>>> >>>> rollback)?
>>> >>>>>
>>> >>>>> Thanks in advance!
>>> >>>>>
>>> >>>>> Cheers,
>>> >>>>> Daniel
>>> >>>>>
>>> >>>>>
>>> >>>>>
>>> >>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>> >>>>>
>>> >>>>>> Hi Daniel,
>>> >>>>>>
>>> >>>>>> Yes, post load callback should be invoked as advertised. I never
>>> >>>>>> personally tried it from a commit catch block, but it should
>>> work. Do
>>> >>>> you
>>> >>>>>> have a code sample? Maybe there is a scenario that we do not
>>> handle.
>>> >>>>>>
>>> >>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>>> >>>> callback
>>> >>>>>>> working or something similar?
>>> >>>>>>
>>> >>>>>> The original callbacks were taken from the JPA spec that doesn't
>>> >> specify
>>> >>>>>> postRollback. We've already diverged from JPA by adding PostAdd. I
>>> >>>> think we
>>> >>>>>> might go further to better reflect Cayenne object lifecycle. So I
>>> am
>>> >>>> open
>>> >>>>>> to adding PostRollback in the future (need to think it through
>>> >> though)…
>>> >>>>>>
>>> >>>>>> Andrus
>>> >>>>>>
>>> >>>>>> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <
>>> dscheibe79@googlemail.com
>>> >>>
>>> >>>>>> wrote:
>>> >>>>>>> All,
>>> >>>>>>>
>>> >>>>>>> i'm trying to get the lifecycle listeners working for my use
>>> case and
>>> >>>>>> i've
>>> >>>>>>> come accross a problem. I registered a listener to do some extra
>>> >> stuff
>>> >>>>>> for
>>> >>>>>>> an entity whenever it will be persisted (prePersist) via:
>>> >>>>>>>
>>> >>>>>>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST,
>>> >> Content.class,
>>> >>>>>>> "prePersist");
>>> >>>>>>>
>>> >>>>>>> This get's called as expected and works smoothly.
>>> >>>>>>>
>>> >>>>>>> Now whenever i have the scenario of a CommitException thrown
>>> during
>>> >>>>>>> commitChanges() (for whatever reason, underlying database not
>>> >>>> available,
>>> >>>>>>> etc.) i need to revert some of the stuff i did in the
>>> "prePersist"
>>> >>>>>>> lifefycle callback on the object in question.
>>> >>>>>>>
>>> >>>>>>> Unfortunately i haven't had luck yet to register a lifecycle
>>> listener
>>> >>>>>> that
>>> >>>>>>> will be called in case of a "rollback" through "rollbackChanges".
>>> >>>>>>>
>>> >>>>>>> The documentation states something about "PostLoad" being called
>>> >>>> "Within
>>> >>>>>>> "ObjectContext.rollbackChanges()" after the object is reverted."
>>> >>>>>> (although
>>> >>>>>>> this is from 3.0 i guess it should still apply?
>>> >>>>>>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
>>> >>>>>>>
>>> >>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>>> >>>> callback
>>> >>>>>>> working or something similar? Or did i just hit a bug with the
>>> >> version
>>> >>>>>> i'm
>>> >>>>>>> using?
>>> >>>>>>>
>>> >>>>>>> Any help is much appreciated.
>>> >>>>>>>
>>> >>>>>>> Cheers,
>>> >>>>>>> Daniel
>>> >>>>>>
>>> >>>>>>
>>> >>>>
>>> >>>>
>>> >>
>>> >>
>>>
>>>
>>
>

Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Daniel Scheibe <ds...@googlemail.com>.
Andrus,

i'm making good progress but i have another quick question regarding the
persistency state of an entity instance. When i set a property on my
Content.class (inputStream) that is not a *real* field in (i.e.: mapped via
cayenne) can i somehow flag this entity instance as modified? For instance,
is it safe to mark the object as PersistenceState.MODIFIED from within when
i know there has been a change or is this causing strange side effects?
Otherwise it won't get recognized as a changed objects and i think the
"prePersist" lifecycle callback won't get called too...

Thanks in advance!

Cheers,
Daniel


2013/3/7 Daniel Scheibe <ds...@googlemail.com>

> Thanks Andrus,
>
> i will do a prototype based on your suggestions (looks promising) to see
> if that works for me. I agree on not putting too much logic into the
> Persistent Objects. I just need to expose all of the functionality combined
> through a common interface on top of the actual cayenne/filestore
> implementation as the user of this shouldn't care about how the data is
> stored underneath the surface.
>
> Basically it should be layered like this:
>
> Persistency Layer (combines and abstract all the functionality underneath)
>     - Cayenne Layer
>     - Filestore Layer
>
> Cheers,
> Daniel
>
>
>
> 2013/3/7 Andrus Adamchik <an...@objectstyle.org>
>
>> Understood the scenario.
>>
>> I usually stay away from too much logic like that in the objects
>> themselves and put that in some external handler classes, that can be
>> registered as listeners if needed. It is much easier (and cleaner) to
>> access application-specific "context" inside listeners that are not
>> persistent objects. A good example can be found in the "Combining Listeners
>> with DataChannelFilters" docs:
>>
>>
>> http://cayenne.apache.org/docs/3.1/cayenne-guide/lifecycle-events.html#comining-listeners-with-datachannelfilters
>>
>> In your case you may also implement a DataChannelFilter that does stream
>> processing. E.g.:
>>
>> public class StreamHandler implements DataChannelFilter {
>>
>>     private ThreadLocal<Collection<InputStream>> streams;
>>
>>
>>     // this method collects streams that need to be processed in the
>> current transaction
>>     @PrePersist(entities = Content.class)
>>     @PreUpdate(entities = Content.class)
>>     @PreRemove(entities = Content.class)
>>     void beforeCommit(Content object) {
>>         streams.get().add(object.getStream());
>>     }
>>
>>     @Override
>>     public void init(DataChannel channel) {
>>         streams = new ThreadLocal<Collection<InputStream>>();
>>     }
>>
>>     @Override
>>     public QueryResponse onQuery(ObjectContext originatingContext, Query
>> query, DataChannelFilterChain filterChain) {
>>         return filterChain.onQuery(originatingContext, query);
>>     }
>>
>>     @Override
>>     public GraphDiff onSync(ObjectContext originatingContext, GraphDiff
>> changes, int syncType,
>>             DataChannelFilterChain filterChain) {
>>
>>         // ignore all but commits
>>         if(syncType != FLUSH_CASCADE_SYNC) {
>>             return filterChain.onSync(originatingContext, changes,
>> syncType);
>>         }
>>
>>         streams.set(new ArrayList<InputStream>());
>>
>>         try {
>>             GraphDiff result = filterChain.onSync(originatingContext,
>> changes, syncType);
>>
>>             // SUCCESS: handle streams
>>             return result;
>>         }
>>         catch(CayenneRuntimeException e) {
>>                 // FAILURE: handle streams, rollback the context
>>                 ...
>>                 originatingContext.rollbackChanges();
>>         }
>>         finally {
>>             streams.set(null);
>>         }
>>     }
>>
>> }
>>
>>
>> On Mar 6, 2013, at 6:51 PM, Daniel Scheibe <ds...@googlemail.com>
>> wrote:
>> > Thanks for the quick response, Andrus!
>> >
>> > I just came across the lines you just posted in "ObjectStore / void
>> > objectsRolledBack()" and i guess i'm starting to understand the process
>> a
>> > bit better now. I also agree that changing the behaviour of unsetting
>> > "objectContext" is not an option here.
>> >
>> > Let me describe my use case a bit below:
>> >
>> > I basically have a "Content" entity that reflects a File including the
>> name
>> > and the binary content. Now i have an implementation similar to this:
>> >
>> > public class Content extends _Content
>> > {
>> >    private InputStream _inputStream = null;
>> >
>> >    @Override
>> >    public void setData(String name, InputStream inputStream)
>> >    {
>> >        setName(name);
>> >
>> >        _inputStream = inputStream;
>> >    }
>> >
>> >    /* ... */
>> > }
>> >
>> > Now i have the following scenarios below:
>> >
>> > 1. commitChanges for "Content" entity instance in persistence state
>> "NEW"
>> >    - If an input stream was set, need to write the input stream to my
>> Http
>> > filestore in "prePersist"
>> >
>> > 2. commitChanges fails and rollbackChanges is called for "Content"
>> entity
>> > instance in persistence state "NEW"
>> >    - If an input stream was written to the filestore in 1. i need to
>> > remove the previously written input stream from my Http filestore in
>> > "postRollback" (This is what i'm struggling with...)
>> >
>> > 3. commitChanges for "Content" entity instance in persistence state
>> > "MODIFIED"
>> >    - If an input stream is already linked to this entity instance,
>> > remember it in "prePersist"
>> >    - If a new input stream was set, write the input stream to my Http
>> > filestore in "prePersist"
>> >    - Remove the previously remembered file input stream from my Http
>> > filestore in "postCommit"
>> >
>> > 4. commitChanges for "Content" entity instance in persistence state
>> > "DELETED"
>> >    - If there is an input stream linked to this instance, remove it from
>> > my Http filestore in "postCommit" or "postDelete"
>> >
>> > Furthermore I store what i call "services" as user properties in the
>> object
>> > context instance via ObjectContext.setUserProperty("...") to access
>> these
>> > in my callbacks later on, for example the client wrapper to my Http
>> > filestore. Now in every callback method i grab the filestore client
>> > instance via ObjectContext.getUserProperty("...") to access it.
>> >
>> > What i currently achieved so far is to integrate a callback into my
>> > application to let me know whenever an "Content" entity instance is in
>> > persistence state "NEW" and get's rolled back (scenario described in 1.
>> by
>> > implementing another workaround-lifecycle listener for "postRollback".
>> >
>> > Unfortunately when this is called the object is in the persistence state
>> > TRANSIENT and i no longer have access to the object context as it was
>> unset
>> > before.
>> >
>> > So what i'm trying to achieve is to fully integrate the storage of a
>> binary
>> > file maintaining all aspects (lifecycle) of the "Content" entity and
>> > therefore need to make sure that whenever a commit was successfully
>> > executed the related file content was stored in the Http filestore. If
>> > something goes wrong somewhere in the middle (commitChanges() throws an
>> > exception and i call rollbackChanges()) i try to undo/remove the file
>> from
>> > the Http filestore again if possible and only leaving back an orphaned
>> > (unlinked) file at worst.
>> >
>> > I'm not worried about getting my "postRollback" workaround done but
>> maybe
>> > there is a better option to "inject" services into entities (not using
>> the
>> > ObjectContext as a workaround) or something?
>> >
>> > I hope you get the idea here :)
>> >
>> > Thanks in advance!
>> >
>> > Cheers,
>> > Daniel
>> >
>> > 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>> >
>> >> You are correct. The code that does it goes like this:
>> >>
>> >>  object.setObjectContext(null);
>> >>  object.setObjectId(null);
>> >>  object.setPersistenceState(PersistenceState.TRANSIENT);
>> >>
>> >> I've been thinking about it recently. Rollback by definition wipes out
>> all
>> >> the uncommitted changes to the ObjectContext and its objects. I think
>> we
>> >> might preserve ObjectId property. Keeping it around does no harm, even
>> >> though technically it is a side effect of the object previously being
>> >> registered with the context. We might change this behavior.
>> >>
>> >> I am more hesitant to change the behavior unsetting "objectContext"
>> >> property. It appears to be a more consequential side effect.
>> >>
>> >> I guess we just need an explicit rollback callback invoked prior to
>> >> kicking the object out. I'll put it on the TODO list for 3.2. In the
>> >> meantime I guess you'll have to implement some workaround. If you
>> describe
>> >> what your app does, we can think of a way to catch this.
>> >>
>> >> Cheers,
>> >> Andrus
>> >>
>> >>
>> >> On Mar 6, 2013, at 4:58 PM, Daniel Scheibe <ds...@googlemail.com>
>> >> wrote:
>> >>> Hi Andrus,
>> >>>
>> >>> Exactly, i'm dealing with NEW objects. While trying to build a
>> workaround
>> >>> for a "postRollback" callback i also noticed that the objects are in a
>> >>> TRANSIENT state after rollbackChanges() was executed. Unfortunately
>> >> objects
>> >>> in this state don't contain a lot of useful data anymore (ObjectId,
>> >>> ObjectContext is gone).
>> >>>
>> >>> Cheers,
>> >>> Daniel
>> >>>
>> >>>
>> >>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>> >>>
>> >>>> Haven't tried to run it yet, but rolling back NEW object transfers
>> them
>> >>>> into the TRANSIENT state. For this state change postLoad (or any
>> other
>> >>>> callback) is indeed not invoked. It will be called for rolled back
>> >> MODIFIED
>> >>>> and DELETED objects.
>> >>>>
>> >>>> So in your application, are you dealing with reverting NEW objects
>> too?
>> >>>>
>> >>>> Andrus
>> >>>>
>> >>>> On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <
>> dscheibe79@googlemail.com>
>> >>>> wrote:
>> >>>>
>> >>>>> Hi Andrus,
>> >>>>>
>> >>>>> thanks for your feedback. I tried to dig into the Cayenne source
>> code
>> >>>> from
>> >>>>> the trunk to see if there actually already is a test case for my
>> >>>> scenario.
>> >>>>> Unfortunately i wasn't able to find one so i tried to build one
>> myself
>> >>>>> (should make it fairly easy to test). Please find my test case
>> below (I
>> >>>>> hope it is correct as i'm not yet familiar with the Cayenne source
>> code
>> >>>>> structure)
>> >>>>>
>> >>>>> So here is the code (i implemented it in
>> >>>>> org.apache.cayenne.access.DataContextCallbacksTest):
>> >>>>>
>> >>>>>  public void testPostLoadCallbacks() {
>> >>>>>
>> >>>>>      LifecycleCallbackRegistry registry = runtime
>> >>>>>              .getDataDomain()
>> >>>>>              .getEntityResolver()
>> >>>>>              .getCallbackRegistry();
>> >>>>>
>> >>>>>      // no callbacks
>> >>>>>      Artist a1 = context.newObject(Artist.class);
>> >>>>>      assertTrue(a1.getPostLoaded() == 0);
>> >>>>>
>> >>>>>      try {
>> >>>>>          context.commitChanges();
>> >>>>>      } catch (CayenneRuntimeException cre) {
>> >>>>>          context.rollbackChanges();
>> >>>>>          assertTrue(a1.getPostLoaded() == 0);
>> >>>>>      }
>> >>>>>
>> >>>>>      registry
>> >>>>>              .addListener(LifecycleEvent.POST_LOAD, Artist.class,
>> >>>>> "postLoadCallback");
>> >>>>>
>> >>>>>      Artist a2 = context.newObject(Artist.class);
>> >>>>>      assertTrue(a2.getPostLoaded() == 0);
>> >>>>>
>> >>>>>      try {
>> >>>>>          context.commitChanges();
>> >>>>>      } catch (CayenneRuntimeException cre) {
>> >>>>>          context.rollbackChanges();
>> >>>>>          assertTrue(a2.getPostLoaded() > 0);
>> >>>>>      }
>> >>>>>  }
>> >>>>>
>> >>>>> Should this test pass successfully or did i do something wrong here
>> (I
>> >>>>> assume a2.getPostLoaded() should return a non-zero value after a
>> >>>> rollback)?
>> >>>>>
>> >>>>> Thanks in advance!
>> >>>>>
>> >>>>> Cheers,
>> >>>>> Daniel
>> >>>>>
>> >>>>>
>> >>>>>
>> >>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>> >>>>>
>> >>>>>> Hi Daniel,
>> >>>>>>
>> >>>>>> Yes, post load callback should be invoked as advertised. I never
>> >>>>>> personally tried it from a commit catch block, but it should work.
>> Do
>> >>>> you
>> >>>>>> have a code sample? Maybe there is a scenario that we do not
>> handle.
>> >>>>>>
>> >>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>> >>>> callback
>> >>>>>>> working or something similar?
>> >>>>>>
>> >>>>>> The original callbacks were taken from the JPA spec that doesn't
>> >> specify
>> >>>>>> postRollback. We've already diverged from JPA by adding PostAdd. I
>> >>>> think we
>> >>>>>> might go further to better reflect Cayenne object lifecycle. So I
>> am
>> >>>> open
>> >>>>>> to adding PostRollback in the future (need to think it through
>> >> though)…
>> >>>>>>
>> >>>>>> Andrus
>> >>>>>>
>> >>>>>> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <
>> dscheibe79@googlemail.com
>> >>>
>> >>>>>> wrote:
>> >>>>>>> All,
>> >>>>>>>
>> >>>>>>> i'm trying to get the lifecycle listeners working for my use case
>> and
>> >>>>>> i've
>> >>>>>>> come accross a problem. I registered a listener to do some extra
>> >> stuff
>> >>>>>> for
>> >>>>>>> an entity whenever it will be persisted (prePersist) via:
>> >>>>>>>
>> >>>>>>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST,
>> >> Content.class,
>> >>>>>>> "prePersist");
>> >>>>>>>
>> >>>>>>> This get's called as expected and works smoothly.
>> >>>>>>>
>> >>>>>>> Now whenever i have the scenario of a CommitException thrown
>> during
>> >>>>>>> commitChanges() (for whatever reason, underlying database not
>> >>>> available,
>> >>>>>>> etc.) i need to revert some of the stuff i did in the "prePersist"
>> >>>>>>> lifefycle callback on the object in question.
>> >>>>>>>
>> >>>>>>> Unfortunately i haven't had luck yet to register a lifecycle
>> listener
>> >>>>>> that
>> >>>>>>> will be called in case of a "rollback" through "rollbackChanges".
>> >>>>>>>
>> >>>>>>> The documentation states something about "PostLoad" being called
>> >>>> "Within
>> >>>>>>> "ObjectContext.rollbackChanges()" after the object is reverted."
>> >>>>>> (although
>> >>>>>>> this is from 3.0 i guess it should still apply?
>> >>>>>>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
>> >>>>>>>
>> >>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>> >>>> callback
>> >>>>>>> working or something similar? Or did i just hit a bug with the
>> >> version
>> >>>>>> i'm
>> >>>>>>> using?
>> >>>>>>>
>> >>>>>>> Any help is much appreciated.
>> >>>>>>>
>> >>>>>>> Cheers,
>> >>>>>>> Daniel
>> >>>>>>
>> >>>>>>
>> >>>>
>> >>>>
>> >>
>> >>
>>
>>
>

Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Daniel Scheibe <ds...@googlemail.com>.
Thanks Andrus,

i will do a prototype based on your suggestions (looks promising) to see if
that works for me. I agree on not putting too much logic into the
Persistent Objects. I just need to expose all of the functionality combined
through a common interface on top of the actual cayenne/filestore
implementation as the user of this shouldn't care about how the data is
stored underneath the surface.

Basically it should be layered like this:

Persistency Layer (combines and abstract all the functionality underneath)
    - Cayenne Layer
    - Filestore Layer

Cheers,
Daniel



2013/3/7 Andrus Adamchik <an...@objectstyle.org>

> Understood the scenario.
>
> I usually stay away from too much logic like that in the objects
> themselves and put that in some external handler classes, that can be
> registered as listeners if needed. It is much easier (and cleaner) to
> access application-specific "context" inside listeners that are not
> persistent objects. A good example can be found in the "Combining Listeners
> with DataChannelFilters" docs:
>
>
> http://cayenne.apache.org/docs/3.1/cayenne-guide/lifecycle-events.html#comining-listeners-with-datachannelfilters
>
> In your case you may also implement a DataChannelFilter that does stream
> processing. E.g.:
>
> public class StreamHandler implements DataChannelFilter {
>
>     private ThreadLocal<Collection<InputStream>> streams;
>
>
>     // this method collects streams that need to be processed in the
> current transaction
>     @PrePersist(entities = Content.class)
>     @PreUpdate(entities = Content.class)
>     @PreRemove(entities = Content.class)
>     void beforeCommit(Content object) {
>         streams.get().add(object.getStream());
>     }
>
>     @Override
>     public void init(DataChannel channel) {
>         streams = new ThreadLocal<Collection<InputStream>>();
>     }
>
>     @Override
>     public QueryResponse onQuery(ObjectContext originatingContext, Query
> query, DataChannelFilterChain filterChain) {
>         return filterChain.onQuery(originatingContext, query);
>     }
>
>     @Override
>     public GraphDiff onSync(ObjectContext originatingContext, GraphDiff
> changes, int syncType,
>             DataChannelFilterChain filterChain) {
>
>         // ignore all but commits
>         if(syncType != FLUSH_CASCADE_SYNC) {
>             return filterChain.onSync(originatingContext, changes,
> syncType);
>         }
>
>         streams.set(new ArrayList<InputStream>());
>
>         try {
>             GraphDiff result = filterChain.onSync(originatingContext,
> changes, syncType);
>
>             // SUCCESS: handle streams
>             return result;
>         }
>         catch(CayenneRuntimeException e) {
>                 // FAILURE: handle streams, rollback the context
>                 ...
>                 originatingContext.rollbackChanges();
>         }
>         finally {
>             streams.set(null);
>         }
>     }
>
> }
>
>
> On Mar 6, 2013, at 6:51 PM, Daniel Scheibe <ds...@googlemail.com>
> wrote:
> > Thanks for the quick response, Andrus!
> >
> > I just came across the lines you just posted in "ObjectStore / void
> > objectsRolledBack()" and i guess i'm starting to understand the process a
> > bit better now. I also agree that changing the behaviour of unsetting
> > "objectContext" is not an option here.
> >
> > Let me describe my use case a bit below:
> >
> > I basically have a "Content" entity that reflects a File including the
> name
> > and the binary content. Now i have an implementation similar to this:
> >
> > public class Content extends _Content
> > {
> >    private InputStream _inputStream = null;
> >
> >    @Override
> >    public void setData(String name, InputStream inputStream)
> >    {
> >        setName(name);
> >
> >        _inputStream = inputStream;
> >    }
> >
> >    /* ... */
> > }
> >
> > Now i have the following scenarios below:
> >
> > 1. commitChanges for "Content" entity instance in persistence state "NEW"
> >    - If an input stream was set, need to write the input stream to my
> Http
> > filestore in "prePersist"
> >
> > 2. commitChanges fails and rollbackChanges is called for "Content" entity
> > instance in persistence state "NEW"
> >    - If an input stream was written to the filestore in 1. i need to
> > remove the previously written input stream from my Http filestore in
> > "postRollback" (This is what i'm struggling with...)
> >
> > 3. commitChanges for "Content" entity instance in persistence state
> > "MODIFIED"
> >    - If an input stream is already linked to this entity instance,
> > remember it in "prePersist"
> >    - If a new input stream was set, write the input stream to my Http
> > filestore in "prePersist"
> >    - Remove the previously remembered file input stream from my Http
> > filestore in "postCommit"
> >
> > 4. commitChanges for "Content" entity instance in persistence state
> > "DELETED"
> >    - If there is an input stream linked to this instance, remove it from
> > my Http filestore in "postCommit" or "postDelete"
> >
> > Furthermore I store what i call "services" as user properties in the
> object
> > context instance via ObjectContext.setUserProperty("...") to access these
> > in my callbacks later on, for example the client wrapper to my Http
> > filestore. Now in every callback method i grab the filestore client
> > instance via ObjectContext.getUserProperty("...") to access it.
> >
> > What i currently achieved so far is to integrate a callback into my
> > application to let me know whenever an "Content" entity instance is in
> > persistence state "NEW" and get's rolled back (scenario described in 1.
> by
> > implementing another workaround-lifecycle listener for "postRollback".
> >
> > Unfortunately when this is called the object is in the persistence state
> > TRANSIENT and i no longer have access to the object context as it was
> unset
> > before.
> >
> > So what i'm trying to achieve is to fully integrate the storage of a
> binary
> > file maintaining all aspects (lifecycle) of the "Content" entity and
> > therefore need to make sure that whenever a commit was successfully
> > executed the related file content was stored in the Http filestore. If
> > something goes wrong somewhere in the middle (commitChanges() throws an
> > exception and i call rollbackChanges()) i try to undo/remove the file
> from
> > the Http filestore again if possible and only leaving back an orphaned
> > (unlinked) file at worst.
> >
> > I'm not worried about getting my "postRollback" workaround done but maybe
> > there is a better option to "inject" services into entities (not using
> the
> > ObjectContext as a workaround) or something?
> >
> > I hope you get the idea here :)
> >
> > Thanks in advance!
> >
> > Cheers,
> > Daniel
> >
> > 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
> >
> >> You are correct. The code that does it goes like this:
> >>
> >>  object.setObjectContext(null);
> >>  object.setObjectId(null);
> >>  object.setPersistenceState(PersistenceState.TRANSIENT);
> >>
> >> I've been thinking about it recently. Rollback by definition wipes out
> all
> >> the uncommitted changes to the ObjectContext and its objects. I think we
> >> might preserve ObjectId property. Keeping it around does no harm, even
> >> though technically it is a side effect of the object previously being
> >> registered with the context. We might change this behavior.
> >>
> >> I am more hesitant to change the behavior unsetting "objectContext"
> >> property. It appears to be a more consequential side effect.
> >>
> >> I guess we just need an explicit rollback callback invoked prior to
> >> kicking the object out. I'll put it on the TODO list for 3.2. In the
> >> meantime I guess you'll have to implement some workaround. If you
> describe
> >> what your app does, we can think of a way to catch this.
> >>
> >> Cheers,
> >> Andrus
> >>
> >>
> >> On Mar 6, 2013, at 4:58 PM, Daniel Scheibe <ds...@googlemail.com>
> >> wrote:
> >>> Hi Andrus,
> >>>
> >>> Exactly, i'm dealing with NEW objects. While trying to build a
> workaround
> >>> for a "postRollback" callback i also noticed that the objects are in a
> >>> TRANSIENT state after rollbackChanges() was executed. Unfortunately
> >> objects
> >>> in this state don't contain a lot of useful data anymore (ObjectId,
> >>> ObjectContext is gone).
> >>>
> >>> Cheers,
> >>> Daniel
> >>>
> >>>
> >>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
> >>>
> >>>> Haven't tried to run it yet, but rolling back NEW object transfers
> them
> >>>> into the TRANSIENT state. For this state change postLoad (or any other
> >>>> callback) is indeed not invoked. It will be called for rolled back
> >> MODIFIED
> >>>> and DELETED objects.
> >>>>
> >>>> So in your application, are you dealing with reverting NEW objects
> too?
> >>>>
> >>>> Andrus
> >>>>
> >>>> On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <dscheibe79@googlemail.com
> >
> >>>> wrote:
> >>>>
> >>>>> Hi Andrus,
> >>>>>
> >>>>> thanks for your feedback. I tried to dig into the Cayenne source code
> >>>> from
> >>>>> the trunk to see if there actually already is a test case for my
> >>>> scenario.
> >>>>> Unfortunately i wasn't able to find one so i tried to build one
> myself
> >>>>> (should make it fairly easy to test). Please find my test case below
> (I
> >>>>> hope it is correct as i'm not yet familiar with the Cayenne source
> code
> >>>>> structure)
> >>>>>
> >>>>> So here is the code (i implemented it in
> >>>>> org.apache.cayenne.access.DataContextCallbacksTest):
> >>>>>
> >>>>>  public void testPostLoadCallbacks() {
> >>>>>
> >>>>>      LifecycleCallbackRegistry registry = runtime
> >>>>>              .getDataDomain()
> >>>>>              .getEntityResolver()
> >>>>>              .getCallbackRegistry();
> >>>>>
> >>>>>      // no callbacks
> >>>>>      Artist a1 = context.newObject(Artist.class);
> >>>>>      assertTrue(a1.getPostLoaded() == 0);
> >>>>>
> >>>>>      try {
> >>>>>          context.commitChanges();
> >>>>>      } catch (CayenneRuntimeException cre) {
> >>>>>          context.rollbackChanges();
> >>>>>          assertTrue(a1.getPostLoaded() == 0);
> >>>>>      }
> >>>>>
> >>>>>      registry
> >>>>>              .addListener(LifecycleEvent.POST_LOAD, Artist.class,
> >>>>> "postLoadCallback");
> >>>>>
> >>>>>      Artist a2 = context.newObject(Artist.class);
> >>>>>      assertTrue(a2.getPostLoaded() == 0);
> >>>>>
> >>>>>      try {
> >>>>>          context.commitChanges();
> >>>>>      } catch (CayenneRuntimeException cre) {
> >>>>>          context.rollbackChanges();
> >>>>>          assertTrue(a2.getPostLoaded() > 0);
> >>>>>      }
> >>>>>  }
> >>>>>
> >>>>> Should this test pass successfully or did i do something wrong here
> (I
> >>>>> assume a2.getPostLoaded() should return a non-zero value after a
> >>>> rollback)?
> >>>>>
> >>>>> Thanks in advance!
> >>>>>
> >>>>> Cheers,
> >>>>> Daniel
> >>>>>
> >>>>>
> >>>>>
> >>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
> >>>>>
> >>>>>> Hi Daniel,
> >>>>>>
> >>>>>> Yes, post load callback should be invoked as advertised. I never
> >>>>>> personally tried it from a commit catch block, but it should work.
> Do
> >>>> you
> >>>>>> have a code sample? Maybe there is a scenario that we do not handle.
> >>>>>>
> >>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
> >>>> callback
> >>>>>>> working or something similar?
> >>>>>>
> >>>>>> The original callbacks were taken from the JPA spec that doesn't
> >> specify
> >>>>>> postRollback. We've already diverged from JPA by adding PostAdd. I
> >>>> think we
> >>>>>> might go further to better reflect Cayenne object lifecycle. So I am
> >>>> open
> >>>>>> to adding PostRollback in the future (need to think it through
> >> though)…
> >>>>>>
> >>>>>> Andrus
> >>>>>>
> >>>>>> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <
> dscheibe79@googlemail.com
> >>>
> >>>>>> wrote:
> >>>>>>> All,
> >>>>>>>
> >>>>>>> i'm trying to get the lifecycle listeners working for my use case
> and
> >>>>>> i've
> >>>>>>> come accross a problem. I registered a listener to do some extra
> >> stuff
> >>>>>> for
> >>>>>>> an entity whenever it will be persisted (prePersist) via:
> >>>>>>>
> >>>>>>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST,
> >> Content.class,
> >>>>>>> "prePersist");
> >>>>>>>
> >>>>>>> This get's called as expected and works smoothly.
> >>>>>>>
> >>>>>>> Now whenever i have the scenario of a CommitException thrown during
> >>>>>>> commitChanges() (for whatever reason, underlying database not
> >>>> available,
> >>>>>>> etc.) i need to revert some of the stuff i did in the "prePersist"
> >>>>>>> lifefycle callback on the object in question.
> >>>>>>>
> >>>>>>> Unfortunately i haven't had luck yet to register a lifecycle
> listener
> >>>>>> that
> >>>>>>> will be called in case of a "rollback" through "rollbackChanges".
> >>>>>>>
> >>>>>>> The documentation states something about "PostLoad" being called
> >>>> "Within
> >>>>>>> "ObjectContext.rollbackChanges()" after the object is reverted."
> >>>>>> (although
> >>>>>>> this is from 3.0 i guess it should still apply?
> >>>>>>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
> >>>>>>>
> >>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
> >>>> callback
> >>>>>>> working or something similar? Or did i just hit a bug with the
> >> version
> >>>>>> i'm
> >>>>>>> using?
> >>>>>>>
> >>>>>>> Any help is much appreciated.
> >>>>>>>
> >>>>>>> Cheers,
> >>>>>>> Daniel
> >>>>>>
> >>>>>>
> >>>>
> >>>>
> >>
> >>
>
>

Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Andrus Adamchik <an...@objectstyle.org>.
Understood the scenario. 

I usually stay away from too much logic like that in the objects themselves and put that in some external handler classes, that can be registered as listeners if needed. It is much easier (and cleaner) to access application-specific "context" inside listeners that are not persistent objects. A good example can be found in the "Combining Listeners with DataChannelFilters" docs:

http://cayenne.apache.org/docs/3.1/cayenne-guide/lifecycle-events.html#comining-listeners-with-datachannelfilters

In your case you may also implement a DataChannelFilter that does stream processing. E.g.:

public class StreamHandler implements DataChannelFilter {

    private ThreadLocal<Collection<InputStream>> streams;


    // this method collects streams that need to be processed in the current transaction
    @PrePersist(entities = Content.class)
    @PreUpdate(entities = Content.class)
    @PreRemove(entities = Content.class)
    void beforeCommit(Content object) {
        streams.get().add(object.getStream());
    }

    @Override
    public void init(DataChannel channel) {
        streams = new ThreadLocal<Collection<InputStream>>();
    }

    @Override
    public QueryResponse onQuery(ObjectContext originatingContext, Query query, DataChannelFilterChain filterChain) {
        return filterChain.onQuery(originatingContext, query);
    }

    @Override
    public GraphDiff onSync(ObjectContext originatingContext, GraphDiff changes, int syncType,
            DataChannelFilterChain filterChain) {

        // ignore all but commits
        if(syncType != FLUSH_CASCADE_SYNC) {
            return filterChain.onSync(originatingContext, changes, syncType);
        }
        
        streams.set(new ArrayList<InputStream>());

        try {
            GraphDiff result = filterChain.onSync(originatingContext, changes, syncType);

            // SUCCESS: handle streams
            return result;
        } 
	catch(CayenneRuntimeException e) {
		// FAILURE: handle streams, rollback the context
		...
		originatingContext.rollbackChanges();
	}
        finally {
            streams.set(null);
        }
    }

}


On Mar 6, 2013, at 6:51 PM, Daniel Scheibe <ds...@googlemail.com> wrote:
> Thanks for the quick response, Andrus!
> 
> I just came across the lines you just posted in "ObjectStore / void
> objectsRolledBack()" and i guess i'm starting to understand the process a
> bit better now. I also agree that changing the behaviour of unsetting
> "objectContext" is not an option here.
> 
> Let me describe my use case a bit below:
> 
> I basically have a "Content" entity that reflects a File including the name
> and the binary content. Now i have an implementation similar to this:
> 
> public class Content extends _Content
> {
>    private InputStream _inputStream = null;
> 
>    @Override
>    public void setData(String name, InputStream inputStream)
>    {
>        setName(name);
> 
>        _inputStream = inputStream;
>    }
> 
>    /* ... */
> }
> 
> Now i have the following scenarios below:
> 
> 1. commitChanges for "Content" entity instance in persistence state "NEW"
>    - If an input stream was set, need to write the input stream to my Http
> filestore in "prePersist"
> 
> 2. commitChanges fails and rollbackChanges is called for "Content" entity
> instance in persistence state "NEW"
>    - If an input stream was written to the filestore in 1. i need to
> remove the previously written input stream from my Http filestore in
> "postRollback" (This is what i'm struggling with...)
> 
> 3. commitChanges for "Content" entity instance in persistence state
> "MODIFIED"
>    - If an input stream is already linked to this entity instance,
> remember it in "prePersist"
>    - If a new input stream was set, write the input stream to my Http
> filestore in "prePersist"
>    - Remove the previously remembered file input stream from my Http
> filestore in "postCommit"
> 
> 4. commitChanges for "Content" entity instance in persistence state
> "DELETED"
>    - If there is an input stream linked to this instance, remove it from
> my Http filestore in "postCommit" or "postDelete"
> 
> Furthermore I store what i call "services" as user properties in the object
> context instance via ObjectContext.setUserProperty("...") to access these
> in my callbacks later on, for example the client wrapper to my Http
> filestore. Now in every callback method i grab the filestore client
> instance via ObjectContext.getUserProperty("...") to access it.
> 
> What i currently achieved so far is to integrate a callback into my
> application to let me know whenever an "Content" entity instance is in
> persistence state "NEW" and get's rolled back (scenario described in 1. by
> implementing another workaround-lifecycle listener for "postRollback".
> 
> Unfortunately when this is called the object is in the persistence state
> TRANSIENT and i no longer have access to the object context as it was unset
> before.
> 
> So what i'm trying to achieve is to fully integrate the storage of a binary
> file maintaining all aspects (lifecycle) of the "Content" entity and
> therefore need to make sure that whenever a commit was successfully
> executed the related file content was stored in the Http filestore. If
> something goes wrong somewhere in the middle (commitChanges() throws an
> exception and i call rollbackChanges()) i try to undo/remove the file from
> the Http filestore again if possible and only leaving back an orphaned
> (unlinked) file at worst.
> 
> I'm not worried about getting my "postRollback" workaround done but maybe
> there is a better option to "inject" services into entities (not using the
> ObjectContext as a workaround) or something?
> 
> I hope you get the idea here :)
> 
> Thanks in advance!
> 
> Cheers,
> Daniel
> 
> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
> 
>> You are correct. The code that does it goes like this:
>> 
>>  object.setObjectContext(null);
>>  object.setObjectId(null);
>>  object.setPersistenceState(PersistenceState.TRANSIENT);
>> 
>> I've been thinking about it recently. Rollback by definition wipes out all
>> the uncommitted changes to the ObjectContext and its objects. I think we
>> might preserve ObjectId property. Keeping it around does no harm, even
>> though technically it is a side effect of the object previously being
>> registered with the context. We might change this behavior.
>> 
>> I am more hesitant to change the behavior unsetting "objectContext"
>> property. It appears to be a more consequential side effect.
>> 
>> I guess we just need an explicit rollback callback invoked prior to
>> kicking the object out. I'll put it on the TODO list for 3.2. In the
>> meantime I guess you'll have to implement some workaround. If you describe
>> what your app does, we can think of a way to catch this.
>> 
>> Cheers,
>> Andrus
>> 
>> 
>> On Mar 6, 2013, at 4:58 PM, Daniel Scheibe <ds...@googlemail.com>
>> wrote:
>>> Hi Andrus,
>>> 
>>> Exactly, i'm dealing with NEW objects. While trying to build a workaround
>>> for a "postRollback" callback i also noticed that the objects are in a
>>> TRANSIENT state after rollbackChanges() was executed. Unfortunately
>> objects
>>> in this state don't contain a lot of useful data anymore (ObjectId,
>>> ObjectContext is gone).
>>> 
>>> Cheers,
>>> Daniel
>>> 
>>> 
>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>> 
>>>> Haven't tried to run it yet, but rolling back NEW object transfers them
>>>> into the TRANSIENT state. For this state change postLoad (or any other
>>>> callback) is indeed not invoked. It will be called for rolled back
>> MODIFIED
>>>> and DELETED objects.
>>>> 
>>>> So in your application, are you dealing with reverting NEW objects too?
>>>> 
>>>> Andrus
>>>> 
>>>> On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <ds...@googlemail.com>
>>>> wrote:
>>>> 
>>>>> Hi Andrus,
>>>>> 
>>>>> thanks for your feedback. I tried to dig into the Cayenne source code
>>>> from
>>>>> the trunk to see if there actually already is a test case for my
>>>> scenario.
>>>>> Unfortunately i wasn't able to find one so i tried to build one myself
>>>>> (should make it fairly easy to test). Please find my test case below (I
>>>>> hope it is correct as i'm not yet familiar with the Cayenne source code
>>>>> structure)
>>>>> 
>>>>> So here is the code (i implemented it in
>>>>> org.apache.cayenne.access.DataContextCallbacksTest):
>>>>> 
>>>>>  public void testPostLoadCallbacks() {
>>>>> 
>>>>>      LifecycleCallbackRegistry registry = runtime
>>>>>              .getDataDomain()
>>>>>              .getEntityResolver()
>>>>>              .getCallbackRegistry();
>>>>> 
>>>>>      // no callbacks
>>>>>      Artist a1 = context.newObject(Artist.class);
>>>>>      assertTrue(a1.getPostLoaded() == 0);
>>>>> 
>>>>>      try {
>>>>>          context.commitChanges();
>>>>>      } catch (CayenneRuntimeException cre) {
>>>>>          context.rollbackChanges();
>>>>>          assertTrue(a1.getPostLoaded() == 0);
>>>>>      }
>>>>> 
>>>>>      registry
>>>>>              .addListener(LifecycleEvent.POST_LOAD, Artist.class,
>>>>> "postLoadCallback");
>>>>> 
>>>>>      Artist a2 = context.newObject(Artist.class);
>>>>>      assertTrue(a2.getPostLoaded() == 0);
>>>>> 
>>>>>      try {
>>>>>          context.commitChanges();
>>>>>      } catch (CayenneRuntimeException cre) {
>>>>>          context.rollbackChanges();
>>>>>          assertTrue(a2.getPostLoaded() > 0);
>>>>>      }
>>>>>  }
>>>>> 
>>>>> Should this test pass successfully or did i do something wrong here (I
>>>>> assume a2.getPostLoaded() should return a non-zero value after a
>>>> rollback)?
>>>>> 
>>>>> Thanks in advance!
>>>>> 
>>>>> Cheers,
>>>>> Daniel
>>>>> 
>>>>> 
>>>>> 
>>>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>>>> 
>>>>>> Hi Daniel,
>>>>>> 
>>>>>> Yes, post load callback should be invoked as advertised. I never
>>>>>> personally tried it from a commit catch block, but it should work. Do
>>>> you
>>>>>> have a code sample? Maybe there is a scenario that we do not handle.
>>>>>> 
>>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>>>> callback
>>>>>>> working or something similar?
>>>>>> 
>>>>>> The original callbacks were taken from the JPA spec that doesn't
>> specify
>>>>>> postRollback. We've already diverged from JPA by adding PostAdd. I
>>>> think we
>>>>>> might go further to better reflect Cayenne object lifecycle. So I am
>>>> open
>>>>>> to adding PostRollback in the future (need to think it through
>> though)…
>>>>>> 
>>>>>> Andrus
>>>>>> 
>>>>>> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <dscheibe79@googlemail.com
>>> 
>>>>>> wrote:
>>>>>>> All,
>>>>>>> 
>>>>>>> i'm trying to get the lifecycle listeners working for my use case and
>>>>>> i've
>>>>>>> come accross a problem. I registered a listener to do some extra
>> stuff
>>>>>> for
>>>>>>> an entity whenever it will be persisted (prePersist) via:
>>>>>>> 
>>>>>>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST,
>> Content.class,
>>>>>>> "prePersist");
>>>>>>> 
>>>>>>> This get's called as expected and works smoothly.
>>>>>>> 
>>>>>>> Now whenever i have the scenario of a CommitException thrown during
>>>>>>> commitChanges() (for whatever reason, underlying database not
>>>> available,
>>>>>>> etc.) i need to revert some of the stuff i did in the "prePersist"
>>>>>>> lifefycle callback on the object in question.
>>>>>>> 
>>>>>>> Unfortunately i haven't had luck yet to register a lifecycle listener
>>>>>> that
>>>>>>> will be called in case of a "rollback" through "rollbackChanges".
>>>>>>> 
>>>>>>> The documentation states something about "PostLoad" being called
>>>> "Within
>>>>>>> "ObjectContext.rollbackChanges()" after the object is reverted."
>>>>>> (although
>>>>>>> this is from 3.0 i guess it should still apply?
>>>>>>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
>>>>>>> 
>>>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>>>> callback
>>>>>>> working or something similar? Or did i just hit a bug with the
>> version
>>>>>> i'm
>>>>>>> using?
>>>>>>> 
>>>>>>> Any help is much appreciated.
>>>>>>> 
>>>>>>> Cheers,
>>>>>>> Daniel
>>>>>> 
>>>>>> 
>>>> 
>>>> 
>> 
>> 


Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Daniel Scheibe <ds...@googlemail.com>.
Thanks for the quick response, Andrus!

I just came across the lines you just posted in "ObjectStore / void
objectsRolledBack()" and i guess i'm starting to understand the process a
bit better now. I also agree that changing the behaviour of unsetting
"objectContext" is not an option here.

Let me describe my use case a bit below:

I basically have a "Content" entity that reflects a File including the name
and the binary content. Now i have an implementation similar to this:

public class Content extends _Content
{
    private InputStream _inputStream = null;

    @Override
    public void setData(String name, InputStream inputStream)
    {
        setName(name);

        _inputStream = inputStream;
    }

    /* ... */
}

Now i have the following scenarios below:

1. commitChanges for "Content" entity instance in persistence state "NEW"
    - If an input stream was set, need to write the input stream to my Http
filestore in "prePersist"

2. commitChanges fails and rollbackChanges is called for "Content" entity
instance in persistence state "NEW"
    - If an input stream was written to the filestore in 1. i need to
remove the previously written input stream from my Http filestore in
"postRollback" (This is what i'm struggling with...)

3. commitChanges for "Content" entity instance in persistence state
"MODIFIED"
    - If an input stream is already linked to this entity instance,
remember it in "prePersist"
    - If a new input stream was set, write the input stream to my Http
filestore in "prePersist"
    - Remove the previously remembered file input stream from my Http
filestore in "postCommit"

4. commitChanges for "Content" entity instance in persistence state
"DELETED"
    - If there is an input stream linked to this instance, remove it from
my Http filestore in "postCommit" or "postDelete"

Furthermore I store what i call "services" as user properties in the object
context instance via ObjectContext.setUserProperty("...") to access these
in my callbacks later on, for example the client wrapper to my Http
filestore. Now in every callback method i grab the filestore client
instance via ObjectContext.getUserProperty("...") to access it.

What i currently achieved so far is to integrate a callback into my
application to let me know whenever an "Content" entity instance is in
persistence state "NEW" and get's rolled back (scenario described in 1. by
implementing another workaround-lifecycle listener for "postRollback".

Unfortunately when this is called the object is in the persistence state
TRANSIENT and i no longer have access to the object context as it was unset
before.

So what i'm trying to achieve is to fully integrate the storage of a binary
file maintaining all aspects (lifecycle) of the "Content" entity and
therefore need to make sure that whenever a commit was successfully
executed the related file content was stored in the Http filestore. If
something goes wrong somewhere in the middle (commitChanges() throws an
exception and i call rollbackChanges()) i try to undo/remove the file from
the Http filestore again if possible and only leaving back an orphaned
(unlinked) file at worst.

I'm not worried about getting my "postRollback" workaround done but maybe
there is a better option to "inject" services into entities (not using the
ObjectContext as a workaround) or something?

I hope you get the idea here :)

Thanks in advance!

Cheers,
Daniel

2013/3/6 Andrus Adamchik <an...@objectstyle.org>

> You are correct. The code that does it goes like this:
>
>   object.setObjectContext(null);
>   object.setObjectId(null);
>   object.setPersistenceState(PersistenceState.TRANSIENT);
>
> I've been thinking about it recently. Rollback by definition wipes out all
> the uncommitted changes to the ObjectContext and its objects. I think we
> might preserve ObjectId property. Keeping it around does no harm, even
> though technically it is a side effect of the object previously being
> registered with the context. We might change this behavior.
>
> I am more hesitant to change the behavior unsetting "objectContext"
> property. It appears to be a more consequential side effect.
>
> I guess we just need an explicit rollback callback invoked prior to
> kicking the object out. I'll put it on the TODO list for 3.2. In the
> meantime I guess you'll have to implement some workaround. If you describe
> what your app does, we can think of a way to catch this.
>
> Cheers,
> Andrus
>
>
> On Mar 6, 2013, at 4:58 PM, Daniel Scheibe <ds...@googlemail.com>
> wrote:
> > Hi Andrus,
> >
> > Exactly, i'm dealing with NEW objects. While trying to build a workaround
> > for a "postRollback" callback i also noticed that the objects are in a
> > TRANSIENT state after rollbackChanges() was executed. Unfortunately
> objects
> > in this state don't contain a lot of useful data anymore (ObjectId,
> > ObjectContext is gone).
> >
> > Cheers,
> > Daniel
> >
> >
> > 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
> >
> >> Haven't tried to run it yet, but rolling back NEW object transfers them
> >> into the TRANSIENT state. For this state change postLoad (or any other
> >> callback) is indeed not invoked. It will be called for rolled back
> MODIFIED
> >> and DELETED objects.
> >>
> >> So in your application, are you dealing with reverting NEW objects too?
> >>
> >> Andrus
> >>
> >> On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <ds...@googlemail.com>
> >> wrote:
> >>
> >>> Hi Andrus,
> >>>
> >>> thanks for your feedback. I tried to dig into the Cayenne source code
> >> from
> >>> the trunk to see if there actually already is a test case for my
> >> scenario.
> >>> Unfortunately i wasn't able to find one so i tried to build one myself
> >>> (should make it fairly easy to test). Please find my test case below (I
> >>> hope it is correct as i'm not yet familiar with the Cayenne source code
> >>> structure)
> >>>
> >>> So here is the code (i implemented it in
> >>> org.apache.cayenne.access.DataContextCallbacksTest):
> >>>
> >>>   public void testPostLoadCallbacks() {
> >>>
> >>>       LifecycleCallbackRegistry registry = runtime
> >>>               .getDataDomain()
> >>>               .getEntityResolver()
> >>>               .getCallbackRegistry();
> >>>
> >>>       // no callbacks
> >>>       Artist a1 = context.newObject(Artist.class);
> >>>       assertTrue(a1.getPostLoaded() == 0);
> >>>
> >>>       try {
> >>>           context.commitChanges();
> >>>       } catch (CayenneRuntimeException cre) {
> >>>           context.rollbackChanges();
> >>>           assertTrue(a1.getPostLoaded() == 0);
> >>>       }
> >>>
> >>>       registry
> >>>               .addListener(LifecycleEvent.POST_LOAD, Artist.class,
> >>> "postLoadCallback");
> >>>
> >>>       Artist a2 = context.newObject(Artist.class);
> >>>       assertTrue(a2.getPostLoaded() == 0);
> >>>
> >>>       try {
> >>>           context.commitChanges();
> >>>       } catch (CayenneRuntimeException cre) {
> >>>           context.rollbackChanges();
> >>>           assertTrue(a2.getPostLoaded() > 0);
> >>>       }
> >>>   }
> >>>
> >>> Should this test pass successfully or did i do something wrong here (I
> >>> assume a2.getPostLoaded() should return a non-zero value after a
> >> rollback)?
> >>>
> >>> Thanks in advance!
> >>>
> >>> Cheers,
> >>> Daniel
> >>>
> >>>
> >>>
> >>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
> >>>
> >>>> Hi Daniel,
> >>>>
> >>>> Yes, post load callback should be invoked as advertised. I never
> >>>> personally tried it from a commit catch block, but it should work. Do
> >> you
> >>>> have a code sample? Maybe there is a scenario that we do not handle.
> >>>>
> >>>>> Is there any chance to get a kind of a "postRollback" lifecycle
> >> callback
> >>>>> working or something similar?
> >>>>
> >>>> The original callbacks were taken from the JPA spec that doesn't
> specify
> >>>> postRollback. We've already diverged from JPA by adding PostAdd. I
> >> think we
> >>>> might go further to better reflect Cayenne object lifecycle. So I am
> >> open
> >>>> to adding PostRollback in the future (need to think it through
> though)…
> >>>>
> >>>> Andrus
> >>>>
> >>>> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <dscheibe79@googlemail.com
> >
> >>>> wrote:
> >>>>> All,
> >>>>>
> >>>>> i'm trying to get the lifecycle listeners working for my use case and
> >>>> i've
> >>>>> come accross a problem. I registered a listener to do some extra
> stuff
> >>>> for
> >>>>> an entity whenever it will be persisted (prePersist) via:
> >>>>>
> >>>>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST,
> Content.class,
> >>>>> "prePersist");
> >>>>>
> >>>>> This get's called as expected and works smoothly.
> >>>>>
> >>>>> Now whenever i have the scenario of a CommitException thrown during
> >>>>> commitChanges() (for whatever reason, underlying database not
> >> available,
> >>>>> etc.) i need to revert some of the stuff i did in the "prePersist"
> >>>>> lifefycle callback on the object in question.
> >>>>>
> >>>>> Unfortunately i haven't had luck yet to register a lifecycle listener
> >>>> that
> >>>>> will be called in case of a "rollback" through "rollbackChanges".
> >>>>>
> >>>>> The documentation states something about "PostLoad" being called
> >> "Within
> >>>>> "ObjectContext.rollbackChanges()" after the object is reverted."
> >>>> (although
> >>>>> this is from 3.0 i guess it should still apply?
> >>>>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
> >>>>>
> >>>>> Is there any chance to get a kind of a "postRollback" lifecycle
> >> callback
> >>>>> working or something similar? Or did i just hit a bug with the
> version
> >>>> i'm
> >>>>> using?
> >>>>>
> >>>>> Any help is much appreciated.
> >>>>>
> >>>>> Cheers,
> >>>>> Daniel
> >>>>
> >>>>
> >>
> >>
>
>

Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Andrus Adamchik <an...@objectstyle.org>.
You are correct. The code that does it goes like this:

  object.setObjectContext(null);
  object.setObjectId(null);
  object.setPersistenceState(PersistenceState.TRANSIENT);

I've been thinking about it recently. Rollback by definition wipes out all the uncommitted changes to the ObjectContext and its objects. I think we might preserve ObjectId property. Keeping it around does no harm, even though technically it is a side effect of the object previously being registered with the context. We might change this behavior.

I am more hesitant to change the behavior unsetting "objectContext" property. It appears to be a more consequential side effect.

I guess we just need an explicit rollback callback invoked prior to kicking the object out. I'll put it on the TODO list for 3.2. In the meantime I guess you'll have to implement some workaround. If you describe what your app does, we can think of a way to catch this.

Cheers,
Andrus


On Mar 6, 2013, at 4:58 PM, Daniel Scheibe <ds...@googlemail.com> wrote:
> Hi Andrus,
> 
> Exactly, i'm dealing with NEW objects. While trying to build a workaround
> for a "postRollback" callback i also noticed that the objects are in a
> TRANSIENT state after rollbackChanges() was executed. Unfortunately objects
> in this state don't contain a lot of useful data anymore (ObjectId,
> ObjectContext is gone).
> 
> Cheers,
> Daniel
> 
> 
> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
> 
>> Haven't tried to run it yet, but rolling back NEW object transfers them
>> into the TRANSIENT state. For this state change postLoad (or any other
>> callback) is indeed not invoked. It will be called for rolled back MODIFIED
>> and DELETED objects.
>> 
>> So in your application, are you dealing with reverting NEW objects too?
>> 
>> Andrus
>> 
>> On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <ds...@googlemail.com>
>> wrote:
>> 
>>> Hi Andrus,
>>> 
>>> thanks for your feedback. I tried to dig into the Cayenne source code
>> from
>>> the trunk to see if there actually already is a test case for my
>> scenario.
>>> Unfortunately i wasn't able to find one so i tried to build one myself
>>> (should make it fairly easy to test). Please find my test case below (I
>>> hope it is correct as i'm not yet familiar with the Cayenne source code
>>> structure)
>>> 
>>> So here is the code (i implemented it in
>>> org.apache.cayenne.access.DataContextCallbacksTest):
>>> 
>>>   public void testPostLoadCallbacks() {
>>> 
>>>       LifecycleCallbackRegistry registry = runtime
>>>               .getDataDomain()
>>>               .getEntityResolver()
>>>               .getCallbackRegistry();
>>> 
>>>       // no callbacks
>>>       Artist a1 = context.newObject(Artist.class);
>>>       assertTrue(a1.getPostLoaded() == 0);
>>> 
>>>       try {
>>>           context.commitChanges();
>>>       } catch (CayenneRuntimeException cre) {
>>>           context.rollbackChanges();
>>>           assertTrue(a1.getPostLoaded() == 0);
>>>       }
>>> 
>>>       registry
>>>               .addListener(LifecycleEvent.POST_LOAD, Artist.class,
>>> "postLoadCallback");
>>> 
>>>       Artist a2 = context.newObject(Artist.class);
>>>       assertTrue(a2.getPostLoaded() == 0);
>>> 
>>>       try {
>>>           context.commitChanges();
>>>       } catch (CayenneRuntimeException cre) {
>>>           context.rollbackChanges();
>>>           assertTrue(a2.getPostLoaded() > 0);
>>>       }
>>>   }
>>> 
>>> Should this test pass successfully or did i do something wrong here (I
>>> assume a2.getPostLoaded() should return a non-zero value after a
>> rollback)?
>>> 
>>> Thanks in advance!
>>> 
>>> Cheers,
>>> Daniel
>>> 
>>> 
>>> 
>>> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
>>> 
>>>> Hi Daniel,
>>>> 
>>>> Yes, post load callback should be invoked as advertised. I never
>>>> personally tried it from a commit catch block, but it should work. Do
>> you
>>>> have a code sample? Maybe there is a scenario that we do not handle.
>>>> 
>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>> callback
>>>>> working or something similar?
>>>> 
>>>> The original callbacks were taken from the JPA spec that doesn't specify
>>>> postRollback. We've already diverged from JPA by adding PostAdd. I
>> think we
>>>> might go further to better reflect Cayenne object lifecycle. So I am
>> open
>>>> to adding PostRollback in the future (need to think it through though)…
>>>> 
>>>> Andrus
>>>> 
>>>> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <ds...@googlemail.com>
>>>> wrote:
>>>>> All,
>>>>> 
>>>>> i'm trying to get the lifecycle listeners working for my use case and
>>>> i've
>>>>> come accross a problem. I registered a listener to do some extra stuff
>>>> for
>>>>> an entity whenever it will be persisted (prePersist) via:
>>>>> 
>>>>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST, Content.class,
>>>>> "prePersist");
>>>>> 
>>>>> This get's called as expected and works smoothly.
>>>>> 
>>>>> Now whenever i have the scenario of a CommitException thrown during
>>>>> commitChanges() (for whatever reason, underlying database not
>> available,
>>>>> etc.) i need to revert some of the stuff i did in the "prePersist"
>>>>> lifefycle callback on the object in question.
>>>>> 
>>>>> Unfortunately i haven't had luck yet to register a lifecycle listener
>>>> that
>>>>> will be called in case of a "rollback" through "rollbackChanges".
>>>>> 
>>>>> The documentation states something about "PostLoad" being called
>> "Within
>>>>> "ObjectContext.rollbackChanges()" after the object is reverted."
>>>> (although
>>>>> this is from 3.0 i guess it should still apply?
>>>>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
>>>>> 
>>>>> Is there any chance to get a kind of a "postRollback" lifecycle
>> callback
>>>>> working or something similar? Or did i just hit a bug with the version
>>>> i'm
>>>>> using?
>>>>> 
>>>>> Any help is much appreciated.
>>>>> 
>>>>> Cheers,
>>>>> Daniel
>>>> 
>>>> 
>> 
>> 


Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Daniel Scheibe <ds...@googlemail.com>.
Hi Andrus,

Exactly, i'm dealing with NEW objects. While trying to build a workaround
for a "postRollback" callback i also noticed that the objects are in a
TRANSIENT state after rollbackChanges() was executed. Unfortunately objects
in this state don't contain a lot of useful data anymore (ObjectId,
ObjectContext is gone).

Cheers,
Daniel


2013/3/6 Andrus Adamchik <an...@objectstyle.org>

> Haven't tried to run it yet, but rolling back NEW object transfers them
> into the TRANSIENT state. For this state change postLoad (or any other
> callback) is indeed not invoked. It will be called for rolled back MODIFIED
> and DELETED objects.
>
> So in your application, are you dealing with reverting NEW objects too?
>
> Andrus
>
> On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <ds...@googlemail.com>
> wrote:
>
> > Hi Andrus,
> >
> > thanks for your feedback. I tried to dig into the Cayenne source code
> from
> > the trunk to see if there actually already is a test case for my
> scenario.
> > Unfortunately i wasn't able to find one so i tried to build one myself
> > (should make it fairly easy to test). Please find my test case below (I
> > hope it is correct as i'm not yet familiar with the Cayenne source code
> > structure)
> >
> > So here is the code (i implemented it in
> > org.apache.cayenne.access.DataContextCallbacksTest):
> >
> >    public void testPostLoadCallbacks() {
> >
> >        LifecycleCallbackRegistry registry = runtime
> >                .getDataDomain()
> >                .getEntityResolver()
> >                .getCallbackRegistry();
> >
> >        // no callbacks
> >        Artist a1 = context.newObject(Artist.class);
> >        assertTrue(a1.getPostLoaded() == 0);
> >
> >        try {
> >            context.commitChanges();
> >        } catch (CayenneRuntimeException cre) {
> >            context.rollbackChanges();
> >            assertTrue(a1.getPostLoaded() == 0);
> >        }
> >
> >        registry
> >                .addListener(LifecycleEvent.POST_LOAD, Artist.class,
> > "postLoadCallback");
> >
> >        Artist a2 = context.newObject(Artist.class);
> >        assertTrue(a2.getPostLoaded() == 0);
> >
> >        try {
> >            context.commitChanges();
> >        } catch (CayenneRuntimeException cre) {
> >            context.rollbackChanges();
> >            assertTrue(a2.getPostLoaded() > 0);
> >        }
> >    }
> >
> > Should this test pass successfully or did i do something wrong here (I
> > assume a2.getPostLoaded() should return a non-zero value after a
> rollback)?
> >
> > Thanks in advance!
> >
> > Cheers,
> > Daniel
> >
> >
> >
> > 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
> >
> >> Hi Daniel,
> >>
> >> Yes, post load callback should be invoked as advertised. I never
> >> personally tried it from a commit catch block, but it should work. Do
> you
> >> have a code sample? Maybe there is a scenario that we do not handle.
> >>
> >>> Is there any chance to get a kind of a "postRollback" lifecycle
> callback
> >>> working or something similar?
> >>
> >> The original callbacks were taken from the JPA spec that doesn't specify
> >> postRollback. We've already diverged from JPA by adding PostAdd. I
> think we
> >> might go further to better reflect Cayenne object lifecycle. So I am
> open
> >> to adding PostRollback in the future (need to think it through though)…
> >>
> >> Andrus
> >>
> >> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <ds...@googlemail.com>
> >> wrote:
> >>> All,
> >>>
> >>> i'm trying to get the lifecycle listeners working for my use case and
> >> i've
> >>> come accross a problem. I registered a listener to do some extra stuff
> >> for
> >>> an entity whenever it will be persisted (prePersist) via:
> >>>
> >>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST, Content.class,
> >>> "prePersist");
> >>>
> >>> This get's called as expected and works smoothly.
> >>>
> >>> Now whenever i have the scenario of a CommitException thrown during
> >>> commitChanges() (for whatever reason, underlying database not
> available,
> >>> etc.) i need to revert some of the stuff i did in the "prePersist"
> >>> lifefycle callback on the object in question.
> >>>
> >>> Unfortunately i haven't had luck yet to register a lifecycle listener
> >> that
> >>> will be called in case of a "rollback" through "rollbackChanges".
> >>>
> >>> The documentation states something about "PostLoad" being called
> "Within
> >>> "ObjectContext.rollbackChanges()" after the object is reverted."
> >> (although
> >>> this is from 3.0 i guess it should still apply?
> >>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
> >>>
> >>> Is there any chance to get a kind of a "postRollback" lifecycle
> callback
> >>> working or something similar? Or did i just hit a bug with the version
> >> i'm
> >>> using?
> >>>
> >>> Any help is much appreciated.
> >>>
> >>> Cheers,
> >>> Daniel
> >>
> >>
>
>

Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Andrus Adamchik <an...@objectstyle.org>.
Haven't tried to run it yet, but rolling back NEW object transfers them into the TRANSIENT state. For this state change postLoad (or any other callback) is indeed not invoked. It will be called for rolled back MODIFIED and DELETED objects.

So in your application, are you dealing with reverting NEW objects too?

Andrus

On Mar 6, 2013, at 2:16 PM, Daniel Scheibe <ds...@googlemail.com> wrote:

> Hi Andrus,
> 
> thanks for your feedback. I tried to dig into the Cayenne source code from
> the trunk to see if there actually already is a test case for my scenario.
> Unfortunately i wasn't able to find one so i tried to build one myself
> (should make it fairly easy to test). Please find my test case below (I
> hope it is correct as i'm not yet familiar with the Cayenne source code
> structure)
> 
> So here is the code (i implemented it in
> org.apache.cayenne.access.DataContextCallbacksTest):
> 
>    public void testPostLoadCallbacks() {
> 
>        LifecycleCallbackRegistry registry = runtime
>                .getDataDomain()
>                .getEntityResolver()
>                .getCallbackRegistry();
> 
>        // no callbacks
>        Artist a1 = context.newObject(Artist.class);
>        assertTrue(a1.getPostLoaded() == 0);
> 
>        try {
>            context.commitChanges();
>        } catch (CayenneRuntimeException cre) {
>            context.rollbackChanges();
>            assertTrue(a1.getPostLoaded() == 0);
>        }
> 
>        registry
>                .addListener(LifecycleEvent.POST_LOAD, Artist.class,
> "postLoadCallback");
> 
>        Artist a2 = context.newObject(Artist.class);
>        assertTrue(a2.getPostLoaded() == 0);
> 
>        try {
>            context.commitChanges();
>        } catch (CayenneRuntimeException cre) {
>            context.rollbackChanges();
>            assertTrue(a2.getPostLoaded() > 0);
>        }
>    }
> 
> Should this test pass successfully or did i do something wrong here (I
> assume a2.getPostLoaded() should return a non-zero value after a rollback)?
> 
> Thanks in advance!
> 
> Cheers,
> Daniel
> 
> 
> 
> 2013/3/6 Andrus Adamchik <an...@objectstyle.org>
> 
>> Hi Daniel,
>> 
>> Yes, post load callback should be invoked as advertised. I never
>> personally tried it from a commit catch block, but it should work. Do you
>> have a code sample? Maybe there is a scenario that we do not handle.
>> 
>>> Is there any chance to get a kind of a "postRollback" lifecycle callback
>>> working or something similar?
>> 
>> The original callbacks were taken from the JPA spec that doesn't specify
>> postRollback. We've already diverged from JPA by adding PostAdd. I think we
>> might go further to better reflect Cayenne object lifecycle. So I am open
>> to adding PostRollback in the future (need to think it through though)…
>> 
>> Andrus
>> 
>> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <ds...@googlemail.com>
>> wrote:
>>> All,
>>> 
>>> i'm trying to get the lifecycle listeners working for my use case and
>> i've
>>> come accross a problem. I registered a listener to do some extra stuff
>> for
>>> an entity whenever it will be persisted (prePersist) via:
>>> 
>>> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST, Content.class,
>>> "prePersist");
>>> 
>>> This get's called as expected and works smoothly.
>>> 
>>> Now whenever i have the scenario of a CommitException thrown during
>>> commitChanges() (for whatever reason, underlying database not available,
>>> etc.) i need to revert some of the stuff i did in the "prePersist"
>>> lifefycle callback on the object in question.
>>> 
>>> Unfortunately i haven't had luck yet to register a lifecycle listener
>> that
>>> will be called in case of a "rollback" through "rollbackChanges".
>>> 
>>> The documentation states something about "PostLoad" being called "Within
>>> "ObjectContext.rollbackChanges()" after the object is reverted."
>> (although
>>> this is from 3.0 i guess it should still apply?
>>> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
>>> 
>>> Is there any chance to get a kind of a "postRollback" lifecycle callback
>>> working or something similar? Or did i just hit a bug with the version
>> i'm
>>> using?
>>> 
>>> Any help is much appreciated.
>>> 
>>> Cheers,
>>> Daniel
>> 
>> 


Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Daniel Scheibe <ds...@googlemail.com>.
Hi Andrus,

thanks for your feedback. I tried to dig into the Cayenne source code from
the trunk to see if there actually already is a test case for my scenario.
Unfortunately i wasn't able to find one so i tried to build one myself
(should make it fairly easy to test). Please find my test case below (I
hope it is correct as i'm not yet familiar with the Cayenne source code
structure)

So here is the code (i implemented it in
org.apache.cayenne.access.DataContextCallbacksTest):

    public void testPostLoadCallbacks() {

        LifecycleCallbackRegistry registry = runtime
                .getDataDomain()
                .getEntityResolver()
                .getCallbackRegistry();

        // no callbacks
        Artist a1 = context.newObject(Artist.class);
        assertTrue(a1.getPostLoaded() == 0);

        try {
            context.commitChanges();
        } catch (CayenneRuntimeException cre) {
            context.rollbackChanges();
            assertTrue(a1.getPostLoaded() == 0);
        }

        registry
                .addListener(LifecycleEvent.POST_LOAD, Artist.class,
"postLoadCallback");

        Artist a2 = context.newObject(Artist.class);
        assertTrue(a2.getPostLoaded() == 0);

        try {
            context.commitChanges();
        } catch (CayenneRuntimeException cre) {
            context.rollbackChanges();
            assertTrue(a2.getPostLoaded() > 0);
        }
    }

Should this test pass successfully or did i do something wrong here (I
assume a2.getPostLoaded() should return a non-zero value after a rollback)?

Thanks in advance!

Cheers,
Daniel



2013/3/6 Andrus Adamchik <an...@objectstyle.org>

> Hi Daniel,
>
> Yes, post load callback should be invoked as advertised. I never
> personally tried it from a commit catch block, but it should work. Do you
> have a code sample? Maybe there is a scenario that we do not handle.
>
> > Is there any chance to get a kind of a "postRollback" lifecycle callback
> > working or something similar?
>
> The original callbacks were taken from the JPA spec that doesn't specify
> postRollback. We've already diverged from JPA by adding PostAdd. I think we
> might go further to better reflect Cayenne object lifecycle. So I am open
> to adding PostRollback in the future (need to think it through though)…
>
> Andrus
>
> On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <ds...@googlemail.com>
> wrote:
> > All,
> >
> > i'm trying to get the lifecycle listeners working for my use case and
> i've
> > come accross a problem. I registered a listener to do some extra stuff
> for
> > an entity whenever it will be persisted (prePersist) via:
> >
> > callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST, Content.class,
> > "prePersist");
> >
> > This get's called as expected and works smoothly.
> >
> > Now whenever i have the scenario of a CommitException thrown during
> > commitChanges() (for whatever reason, underlying database not available,
> > etc.) i need to revert some of the stuff i did in the "prePersist"
> > lifefycle callback on the object in question.
> >
> > Unfortunately i haven't had luck yet to register a lifecycle listener
> that
> > will be called in case of a "rollback" through "rollbackChanges".
> >
> > The documentation states something about "PostLoad" being called "Within
> > "ObjectContext.rollbackChanges()" after the object is reverted."
> (although
> > this is from 3.0 i guess it should still apply?
> > https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
> >
> > Is there any chance to get a kind of a "postRollback" lifecycle callback
> > working or something similar? Or did i just hit a bug with the version
> i'm
> > using?
> >
> > Any help is much appreciated.
> >
> > Cheers,
> > Daniel
>
>

Re: Lifecycle Listeners / cayenne-3.1B1

Posted by Andrus Adamchik <an...@objectstyle.org>.
Hi Daniel,

Yes, post load callback should be invoked as advertised. I never personally tried it from a commit catch block, but it should work. Do you have a code sample? Maybe there is a scenario that we do not handle.

> Is there any chance to get a kind of a "postRollback" lifecycle callback
> working or something similar? 

The original callbacks were taken from the JPA spec that doesn't specify postRollback. We've already diverged from JPA by adding PostAdd. I think we might go further to better reflect Cayenne object lifecycle. So I am open to adding PostRollback in the future (need to think it through though)… 

Andrus

On Mar 5, 2013, at 6:43 PM, Daniel Scheibe <ds...@googlemail.com> wrote:
> All,
> 
> i'm trying to get the lifecycle listeners working for my use case and i've
> come accross a problem. I registered a listener to do some extra stuff for
> an entity whenever it will be persisted (prePersist) via:
> 
> callbackRegistry.addListener(LifecycleEvent.PRE_PERSIST, Content.class,
> "prePersist");
> 
> This get's called as expected and works smoothly.
> 
> Now whenever i have the scenario of a CommitException thrown during
> commitChanges() (for whatever reason, underlying database not available,
> etc.) i need to revert some of the stuff i did in the "prePersist"
> lifefycle callback on the object in question.
> 
> Unfortunately i haven't had luck yet to register a lifecycle listener that
> will be called in case of a "rollback" through "rollbackChanges".
> 
> The documentation states something about "PostLoad" being called "Within
> "ObjectContext.rollbackChanges()" after the object is reverted." (although
> this is from 3.0 i guess it should still apply?
> https://cayenne.apache.org/docs/3.0/lifecycle-callbacks.html)
> 
> Is there any chance to get a kind of a "postRollback" lifecycle callback
> working or something similar? Or did i just hit a bug with the version i'm
> using?
> 
> Any help is much appreciated.
> 
> Cheers,
> Daniel