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