cayenne-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Daniel Scheibe <dscheib...@googlemail.com>
Subject Re: Lifecycle Listeners / cayenne-3.1B1
Date Thu, 07 Mar 2013 10:08:24 GMT
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 <andrus@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 <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
  • Unnamed multipart/alternative (inline, None, 0 bytes)
View raw message