cayenne-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Andrus Adamchik <and...@objectstyle.org>
Subject Re: Lifecycle Listeners / cayenne-3.1B1
Date Thu, 07 Mar 2013 08:34:12 GMT
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 <dscheibe79@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 <andrus@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 <andrus@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 <andrus@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
>>>>>> 
>>>>>> 
>>>> 
>>>> 
>> 
>> 


Mime
View raw message