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