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