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 >>> >>>>>> >>> >>>>>> >>> >>>> >>> >>>> >>> >> >>> >> >>> >>> >> >
