FAQ
Hi all,

I've just gone back to work on a ROP application I have and am again running
into the issue of having to duplicate code between the server model
subclasses and the client model subclasses. I've got a way of working
around that, but I'm thinking this is probably best handled by Cayenne. In
particular, I really don't think that there should be two class hierarchies
for what amounts to a boolean field (remote/not remote).

I think the generated superclass should perform the duty of calling the
appropriate method for reading and writing properties. I think this would
advocate adding an isRemote() method to the ObjectContext interface to avoid
having to do instanceof operations.

There would have to be some sort of unification between PersistentObject and
CayenneDataObject as well. My naïve guess is that the latter could extend
the former, but I haven't investigated whether or not they are compatible.

Does this sound like a reasonable approach? If so, I'll probably dig in on
that.

Thanks,
Kevin

Search Discussions

  • Andrus Adamchik at Oct 27, 2007 at 5:02 pm
    Would be nice if we could make it work... IIRC back in the day when
    ROP was first implemented there were a number of limitations
    preventing DataContext and CayenneDataObject from being used on the
    client, mostly related to the fact that both had direct dependencies
    on the structure of the underlying stack (e.g. referencing DataDomain
    and DataNode). Now we are *mostly* free of those. So I'd be curious
    to try and use DataContext on the client.

    A few issues to pay attention to:

    * Serialization. Current client objects are more lightweight as they
    store their properties in Java fields, vs. a Map used by
    CayenneDataObject. So probably a custom serializer is needed.

    * While supporting a single class hierarchy is a good idea,
    preserving the ability for separate hierarchies is very important -
    users may not want to expose some server methods to the client, and
    generally may want to have two sets of objects with different behavior.

    In any event it is a good idea to reevaluate our options now.

    Andrus


    On Oct 27, 2007, at 7:20 PM, Kevin Menard wrote:

    Hi all,

    I've just gone back to work on a ROP application I have and am
    again running
    into the issue of having to duplicate code between the server model
    subclasses and the client model subclasses. I've got a way of working
    around that, but I'm thinking this is probably best handled by
    Cayenne. In
    particular, I really don't think that there should be two class
    hierarchies
    for what amounts to a boolean field (remote/not remote).

    I think the generated superclass should perform the duty of calling
    the
    appropriate method for reading and writing properties. I think
    this would
    advocate adding an isRemote() method to the ObjectContext interface
    to avoid
    having to do instanceof operations.

    There would have to be some sort of unification between
    PersistentObject and
    CayenneDataObject as well. My naïve guess is that the latter could
    extend
    the former, but I haven't investigated whether or not they are
    compatible.

    Does this sound like a reasonable approach? If so, I'll probably
    dig in on
    that.

    Thanks,
    Kevin
  • Kevin Menard at Oct 27, 2007 at 7:45 pm

    On 10/27/07 1:01 PM, "Andrus Adamchik" wrote:

    * While supporting a single class hierarchy is a good idea,
    preserving the ability for separate hierarchies is very important -
    users may not want to expose some server methods to the client, and
    generally may want to have two sets of objects with different behavior.
    I think I'd like to see this done via the DataMap to tie in a bit more with
    the validation framework. Granted, it'd still have to be supported for
    different sets of utility methods, but it would be worthwhile to be able to
    mark some attributes/relationships only available on server or client. Same
    thing with the read only state.

    --
    Kevin
  • Kevin Menard at Nov 7, 2007 at 6:08 am
    I just spent the better part of this evening trying to get something going.
    Unfortunately, I'm stuck at the moment and it's getting pretty late (or
    early) here. So, I'm just posting what I tried and what my results were.
    If anyone else wants to suggest something, great, if not, I'll prod at it
    more when I can get some time. I'll likely have to defer it and come up
    with some workaround, because I need this portion of the app relying on the
    fix done.

    Anyway . . .

    I took a really naïve approach to start because I just wanted to get
    something going.

    * Made CayenneDataObject extends PersistentObject
    ** Removed any code duplication

    * Updated my data map to use the same class name for the server and client
    classes for all obj entities.

    * Modified the velocity template to combine both server and client class
    info:

    * Static property name fields appear as before
    * All client fields appear
    * All methods perform an instanceof check on the context to choose the
    appropriate code to call like so:

    public BillingInfo getDefaultBillingInfo() {
    if (objectContext instanceof CayenneContext) {
    if(objectContext != null) {
    objectContext.prepareForAccess(this, "defaultBillingInfo",
    true);
    }

    return (BillingInfo) defaultBillingInfo.getValue();
    }
    else {
    return (BillingInfo)readProperty("defaultBillingInfo");
    }
    }


    Problems:

    * Retrieving data from client fails during deep merge on toMany
    relationships. The read property is null causing most of the rest of the
    method to fail.

    * Adding NPE checks for the above gets me to a workable state, but the
    generated class does not work as expected. In particular, all the values
    are in the map (like they should for a server) and all the fields are blank
    (which should not be the case for the client).

    My initial thoughts were that the failures were due to the above code
    snippet failing to do the right thing if the context is null (is there a
    better way to tell if we're executing on the client?). But, the first
    failure occurs well before any of those accessors are called. So, I am a
    bit at a loss.

    Anyway, if Ari or someone else wants to help out with this, I'd certainly
    welcome it.

    --
    Kevin

    On 10/27/07 1:01 PM, "Andrus Adamchik" wrote:

    Would be nice if we could make it work... IIRC back in the day when
    ROP was first implemented there were a number of limitations
    preventing DataContext and CayenneDataObject from being used on the
    client, mostly related to the fact that both had direct dependencies
    on the structure of the underlying stack (e.g. referencing DataDomain
    and DataNode). Now we are *mostly* free of those. So I'd be curious
    to try and use DataContext on the client.

    A few issues to pay attention to:

    * Serialization. Current client objects are more lightweight as they
    store their properties in Java fields, vs. a Map used by
    CayenneDataObject. So probably a custom serializer is needed.

    * While supporting a single class hierarchy is a good idea,
    preserving the ability for separate hierarchies is very important -
    users may not want to expose some server methods to the client, and
    generally may want to have two sets of objects with different behavior.

    In any event it is a good idea to reevaluate our options now.

    Andrus


    On Oct 27, 2007, at 7:20 PM, Kevin Menard wrote:

    Hi all,

    I've just gone back to work on a ROP application I have and am
    again running
    into the issue of having to duplicate code between the server model
    subclasses and the client model subclasses. I've got a way of working
    around that, but I'm thinking this is probably best handled by
    Cayenne. In
    particular, I really don't think that there should be two class
    hierarchies
    for what amounts to a boolean field (remote/not remote).

    I think the generated superclass should perform the duty of calling
    the
    appropriate method for reading and writing properties. I think
    this would
    advocate adding an isRemote() method to the ObjectContext interface
    to avoid
    having to do instanceof operations.

    There would have to be some sort of unification between
    PersistentObject and
    CayenneDataObject as well. My naïve guess is that the latter could
    extend
    the former, but I haven't investigated whether or not they are
    compatible.

    Does this sound like a reasonable approach? If so, I'll probably
    dig in on
    that.

    Thanks,
    Kevin
    --
  • Andrus Adamchik at Nov 7, 2007 at 8:53 am
    Hi Kevin,

    CayenneDataObject approach is fundamentally different from other
    persistent object implementations that are all variations of POJO. So
    I think a different approach may work better - using current client
    objects on the server (instead of using current server objects on the
    client).

    Andrus

    On Nov 7, 2007, at 8:07 AM, Kevin Menard wrote:

    I just spent the better part of this evening trying to get
    something going.
    Unfortunately, I'm stuck at the moment and it's getting pretty late
    (or
    early) here. So, I'm just posting what I tried and what my results
    were.
    If anyone else wants to suggest something, great, if not, I'll prod
    at it
    more when I can get some time. I'll likely have to defer it and
    come up
    with some workaround, because I need this portion of the app
    relying on the
    fix done.

    Anyway . . .

    I took a really naïve approach to start because I just wanted to get
    something going.

    * Made CayenneDataObject extends PersistentObject
    ** Removed any code duplication

    * Updated my data map to use the same class name for the server and
    client
    classes for all obj entities.

    * Modified the velocity template to combine both server and client
    class
    info:

    * Static property name fields appear as before
    * All client fields appear
    * All methods perform an instanceof check on the context to choose the
    appropriate code to call like so:

    public BillingInfo getDefaultBillingInfo() {
    if (objectContext instanceof CayenneContext) {
    if(objectContext != null) {
    objectContext.prepareForAccess(this,
    "defaultBillingInfo",
    true);
    }

    return (BillingInfo) defaultBillingInfo.getValue();
    }
    else {
    return (BillingInfo)readProperty("defaultBillingInfo");
    }
    }


    Problems:

    * Retrieving data from client fails during deep merge on toMany
    relationships. The read property is null causing most of the rest
    of the
    method to fail.

    * Adding NPE checks for the above gets me to a workable state, but the
    generated class does not work as expected. In particular, all the
    values
    are in the map (like they should for a server) and all the fields
    are blank
    (which should not be the case for the client).

    My initial thoughts were that the failures were due to the above code
    snippet failing to do the right thing if the context is null (is
    there a
    better way to tell if we're executing on the client?). But, the first
    failure occurs well before any of those accessors are called. So,
    I am a
    bit at a loss.

    Anyway, if Ari or someone else wants to help out with this, I'd
    certainly
    welcome it.

    --
    Kevin

    On 10/27/07 1:01 PM, "Andrus Adamchik" wrote:

    Would be nice if we could make it work... IIRC back in the day when
    ROP was first implemented there were a number of limitations
    preventing DataContext and CayenneDataObject from being used on the
    client, mostly related to the fact that both had direct dependencies
    on the structure of the underlying stack (e.g. referencing DataDomain
    and DataNode). Now we are *mostly* free of those. So I'd be curious
    to try and use DataContext on the client.

    A few issues to pay attention to:

    * Serialization. Current client objects are more lightweight as they
    store their properties in Java fields, vs. a Map used by
    CayenneDataObject. So probably a custom serializer is needed.

    * While supporting a single class hierarchy is a good idea,
    preserving the ability for separate hierarchies is very important -
    users may not want to expose some server methods to the client, and
    generally may want to have two sets of objects with different
    behavior.

    In any event it is a good idea to reevaluate our options now.

    Andrus


    On Oct 27, 2007, at 7:20 PM, Kevin Menard wrote:

    Hi all,

    I've just gone back to work on a ROP application I have and am
    again running
    into the issue of having to duplicate code between the server model
    subclasses and the client model subclasses. I've got a way of
    working
    around that, but I'm thinking this is probably best handled by
    Cayenne. In
    particular, I really don't think that there should be two class
    hierarchies
    for what amounts to a boolean field (remote/not remote).

    I think the generated superclass should perform the duty of calling
    the
    appropriate method for reading and writing properties. I think
    this would
    advocate adding an isRemote() method to the ObjectContext interface
    to avoid
    having to do instanceof operations.

    There would have to be some sort of unification between
    PersistentObject and
    CayenneDataObject as well. My naïve guess is that the latter could
    extend
    the former, but I haven't investigated whether or not they are
    compatible.

    Does this sound like a reasonable approach? If so, I'll probably
    dig in on
    that.

    Thanks,
    Kevin
  • Kevin Menard at Nov 7, 2007 at 4:15 pm
    Well, I've made marginal progress there as well. I am wrestling with a
    serialization problem right now. I know you had mentioned issues with
    this earlier, but it's the object context that's behaving differently
    from CayenneDataObject.

    I've had a Tapestry 4 app that's used lists of CDOs in @For loops and
    has just worked. As PersistentObjects, they lose their data context on
    deserialization. No custom squeezers are involved. What's really
    interesting is that the contexts are marked as transient in both CDO and
    PO.

    I suspect once that's squared away, the rest won't be nearly as bad.

    --
    Kevin

    -----Original Message-----
    From: Andrus Adamchik
    Sent: Wednesday, November 07, 2007 3:53 AM
    To: dev@cayenne.apache.org
    Subject: Re: Cleaning up ROP

    Hi Kevin,

    CayenneDataObject approach is fundamentally different from other
    persistent object implementations that are all variations of POJO. So
    I think a different approach may work better - using current client
    objects on the server (instead of using current server objects on the
    client).

    Andrus
  • Andrus Adamchik at Nov 7, 2007 at 4:23 pm
    I think we can solve serialization problems... CDO should be
    reattached indirectly inside DataContext.readObject(..). I am
    surprised that didn't work for PersistentObject. You may add a
    breakpoint in DataContext.readObject(..) to see what really happens
    there.

    Andrus

    On Nov 7, 2007, at 6:14 PM, Kevin Menard wrote:

    Well, I've made marginal progress there as well. I am wrestling
    with a
    serialization problem right now. I know you had mentioned issues with
    this earlier, but it's the object context that's behaving differently
    from CayenneDataObject.

    I've had a Tapestry 4 app that's used lists of CDOs in @For loops and
    has just worked. As PersistentObjects, they lose their data
    context on
    deserialization. No custom squeezers are involved. What's really
    interesting is that the contexts are marked as transient in both
    CDO and
    PO.

    I suspect once that's squared away, the rest won't be nearly as bad.

    --
    Kevin

    -----Original Message-----
    From: Andrus Adamchik
    Sent: Wednesday, November 07, 2007 3:53 AM
    To: dev@cayenne.apache.org
    Subject: Re: Cleaning up ROP

    Hi Kevin,

    CayenneDataObject approach is fundamentally different from other
    persistent object implementations that are all variations of POJO. So
    I think a different approach may work better - using current client
    objects on the server (instead of using current server objects on the
    client).

    Andrus
  • Kevin Menard at Nov 7, 2007 at 6:34 pm
    Interestingly, I can't get the code to hit readObject() when using CDO.
    writeObject() is definitely being called from the CDO, though. So, it's
    like something with the Tapesty data squeezer.

    When using a PO, readObject and writeObject are both called (added by
    me).

    In no event does DataContext's readObject or writeObject execute.

    At this point, I'm going to have to table the work for a bit. After two
    days, I'm not much further along than when I started :-/ Maybe the time
    away will help me think about the problem a bit more. In the interim,
    at least the copy & paste method works . . . just more invasive than I
    was hoping for.

    --
    Kevin

    -----Original Message-----
    From: Andrus Adamchik
    Sent: Wednesday, November 07, 2007 11:23 AM
    To: dev@cayenne.apache.org
    Subject: Re: Cleaning up ROP

    I think we can solve serialization problems... CDO should be
    reattached indirectly inside DataContext.readObject(..). I am
    surprised that didn't work for PersistentObject. You may add a
    breakpoint in DataContext.readObject(..) to see what really happens
    there.

    Andrus
  • Aristedes Maniatis at Nov 7, 2007 at 9:57 pm

    On 08/11/2007, at 5:33 AM, Kevin Menard wrote:

    At this point, I'm going to have to table the work for a bit.
    After two
    days, I'm not much further along than when I started :-/ Maybe the
    time
    away will help me think about the problem a bit more. In the interim,
    at least the copy & paste method works . . . just more invasive than I
    was hoping for.
    Perhaps you could post a patch for what you've done so far. This
    feature is important to me too and if there is any time for my team
    or I to work on it we will try. I guess we could even create a
    temporary svn branch for it if that helps, but probably after the
    impending java 5 move would be better.

    Ari


    -------------------------->
    Aristedes Maniatis
    phone +61 2 9660 9700
    PGP fingerprint 08 57 20 4B 80 69 59 E2 A9 BF 2D 48 C2 20 0C C8
  • Kevin Menard at Nov 8, 2007 at 4:35 am
    Looks like the time away paid off. I've made it farther now.

    Turns out the problem was the ivar fields in subclass. Since they get
    loaded lazily, the serialized object differed as soon as I accessed a field.
    So, serialization may not have been broken per se, but it definitely messed
    with the Tapestry squeezer. Modifying the velocity template to mark those
    fields as transient has taken care of the problem for now. I'll see where
    that takes me this weekend.

    Ari, I'll try to cobble together a patch as I clean things up. I agree that
    a new branch may be the best thing. While there is certainly value in
    waiting until the 1.4 and 1.5 modules merge, I don't think it's that
    necessary before we start a new branch.

    --
    Kevin

    On 11/7/07 4:57 PM, "Aristedes Maniatis" wrote:

    On 08/11/2007, at 5:33 AM, Kevin Menard wrote:

    At this point, I'm going to have to table the work for a bit.
    After two
    days, I'm not much further along than when I started :-/ Maybe the
    time
    away will help me think about the problem a bit more. In the interim,
    at least the copy & paste method works . . . just more invasive than I
    was hoping for.
    Perhaps you could post a patch for what you've done so far. This
    feature is important to me too and if there is any time for my team
    or I to work on it we will try. I guess we could even create a
    temporary svn branch for it if that helps, but probably after the
    impending java 5 move would be better.

    Ari


    -------------------------->
    Aristedes Maniatis
    phone +61 2 9660 9700
    PGP fingerprint 08 57 20 4B 80 69 59 E2 A9 BF 2D 48 C2 20 0C C8
    --
  • Kevin Menard at Nov 8, 2007 at 3:19 pm
    Having played with this a bit, I have a better handle on the problem
    now. I also have a much richer appreciation for what CDO does for me
    ;-)

    Making the client objects work on the server seems doable, but does
    require either changes to the velocity templates or the context provided
    to them. Right now, the issues I see are:

    1) Serialization -- Fields are controlled by getters/setters and as such
    should probably be marked transient, allowing a fault to resolve values
    upon deserialization.

    2) Runtime relationships -- Need to have accessor methods in superclass.
    Likely requires change to context.

    3) Reverse relationships -- Need some way of handling this in the
    superclass that doesn't produce a recursive loop.

    #1 I've addressed locally by marking the fields transient. There's
    probably a better way of handling this, but it works for now.

    #2 I've addressed locally by modifying the data map to specify all
    reverse relationships.

    #3 I haven't dealt with yet. A rough approach is to have public
    accessors that take the single target parameter and a set of private
    setters that take the target plus a Boolean that indicates whether to
    set the reverse relationship. This could be done all in the velocity
    template and would take care of the recursive call problem.

    --
    Kevin

    -----Original Message-----
    From: Andrus Adamchik
    Sent: Wednesday, November 07, 2007 3:53 AM
    To: dev@cayenne.apache.org
    Subject: Re: Cleaning up ROP

    Hi Kevin,

    CayenneDataObject approach is fundamentally different from other
    persistent object implementations that are all variations of POJO. So
    I think a different approach may work better - using current client
    objects on the server (instead of using current server objects on the
    client).

    Andrus
  • Andrus Adamchik at Nov 11, 2007 at 2:29 pm

    On Nov 8, 2007, at 10:19 AM, Kevin Menard wrote:
    1) Serialization -- Fields are controlled by getters/setters and as
    such
    should probably be marked transient, allowing a fault to resolve
    values
    upon deserialization.
    I don't understand this one. Why?

    2) Runtime relationships -- Need to have accessor methods in
    superclass.
    Likely requires change to context.
    Yes - probably need special noop property descriptors
    3) Reverse relationships -- Need some way of handling this in the
    superclass that doesn't produce a recursive loop.
    Can't say anything offhand.

    Andrus
  • Andrus Adamchik at Nov 11, 2007 at 2:46 pm
    On Nov 11, 2007, at 9:28 AM, Andrus Adamchik wrote:
    2) Runtime relationships -- Need to have accessor methods in
    superclass.
    Likely requires change to context.
    Yes - probably need special noop property descriptors
    Sorry - I answered that differently in a separate thread after
    thinking about it some more. I think the best solution is adding
    missing fields via class generation.

    Andrus
  • Kevin Menard at Nov 11, 2007 at 4:12 pm

    On 11/11/07 9:28 AM, "Andrus Adamchik" wrote:

    On Nov 8, 2007, at 10:19 AM, Kevin Menard wrote:


    1) Serialization -- Fields are controlled by getters/setters and as
    such
    should probably be marked transient, allowing a fault to resolve
    values
    upon deserialization.
    I don't understand this one. Why?
    Consider the following Tapestry syntax:

    <tr jwcid="@For" source="ognl:paintings" value="ognl:painting">
    <span jwcid="@Insert" value="ognl:painting.title"/>
    </tr>

    I've simplified this because it's not a form and thus no rewind cycle, but
    that's really when the problem occures. The basic issue is that when
    "painting" is assigned and thus serialized (via the Squeezer), the "title"
    field is null. In the pass through the loop, the "title" field is resolved
    and the local field changed from null to whatever the title is. On a form
    rewind, Tapestry reserializes all values to see if anything has changed. It
    essentially has a map keyed on serialized values to objects. When the
    serialized value changes, as is the case here due to the change in the
    "title" field, it can't find it in the map and attempts to reconstruct the
    object.

    This is how the context was carried along using CDO. As it turns out, the
    context was never serialized in my case -- the fully registered object was
    just pulled out of Tapestry's map.

    Hopefully that made some sense.

    --
    Kevin

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupdev @
categoriescayenne
postedOct 27, '07 at 4:21p
activeNov 11, '07 at 4:12p
posts14
users3
websitecayenne.apache.org

People

Translate

site design / logo © 2022 Grokbase