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 <[email protected]>

> 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 <[email protected]>
>
>> 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 <[email protected]>
>>
>>> 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 <[email protected]>
>>> 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 <[email protected]>
>>> >
>>> >> 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 <[email protected]
>>> >
>>> >> 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 <[email protected]>
>>> >>>
>>> >>>> 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 <
>>> [email protected]>
>>> >>>> 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 <[email protected]>
>>> >>>>>
>>> >>>>>> 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 <
>>> [email protected]
>>> >>>
>>> >>>>>> 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
>>> >>>>>>
>>> >>>>>>
>>> >>>>
>>> >>>>
>>> >>
>>> >>
>>>
>>>
>>
>

Reply via email to