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