Oliver Hausler
Oliver Hausler

Reputation: 4977

Objectify Key/Ref roundtrip between backend and client [without GWT]

There are a lot of articles here and all over the web, but these all target different Objectify versions and seem not to work for one or the other reason.

I have an entity, which references another entity (e.g. an Account entity references a User entity):

@Cache
@Entity
public final class Account {

    @Id Long id;
    @Index private Ref<User> user;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

    public User getUser() {
        return user.get();
    }
    public void setUser(User user) {
        this.user = Ref.create(user);
    }

}

I am trying to do this:

  1. From the client, GET the account entity over REST/Google Cloud Endpoints.
  2. Modify the resource.
  3. UPDATE it on the server.

As discussed here Objectify loads object behind Ref<?> even when @Load is not specified above code always returns the referenced user as well, whích I don't want.

One option would be, as @svpino suggested, "Make your @ApiMethod return a different Account object without the user property (thus avoiding fetching the user if you don't need it)." This works as long as I don't want to UPDATE the resource. If I need to UPDATE, the Key/Ref needs to be preserved (even though I don't need it on the client).

One possible approach that I can see would be using Key instead of Ref and rendering a web-safe string, then recreating the user during UPDATE.

private Key<User> user;

public String getUser() {
    return user.toString();
}
public void setUser(String user) {
    this.user = Key.create(user);
}

The string looks like "Key(User(5723348596162560))", but it seems not to be reconstituted (at least I get an exception here, haven't tracked it down yet).

Another approach would be writing an @ApiTransformer, which did not solve the problem either.

Jeff @StickFigure posted several times during the last years and the issue still seems not to be solved.

What's the current state with Objectify 5.0.2 and what's the recommendation for preserving the key between roundtrips, when the key is not needed on the client?

Upvotes: 1

Views: 1214

Answers (3)

Muzikant
Muzikant

Reputation: 8090

You can create a class that extends Ref<User> and use an @ApiTransformer to transfer that class between backend and client

@ApiTransformer(UserRefTransformer.class)
public class UserRef extends LiveRef<User>
{
}

public class UserRefTransformer implements Transformer<UserRef, User>
{
    // Your transformation code goes here
}

Upvotes: 0

Oliver Hausler
Oliver Hausler

Reputation: 4977

The following code serializes an entity object to a web-safe string so it can be transferred over REST. When the entity is sent back to the server, the Ref<> is reconstituted. This way a server-side reference is not lost while the object does a round-trip to the client. This way referenced objects are not transferred to the client and back, but can be "worked" as Ref<> on the client.

@Index private Ref<User> user;

// for serialization
public String getUser() {
    return user.getKey().getString(); // .toWebSafeString() will be added in future version of objectify and .toWebSafeString() will do the same as .getString()
}
public void setUser(String webSafeString) {
    Key<User> key = Key.create(webSafeString);
    this.user = Ref.create(key);
}

Two separate functions (not named well, I admit) are there for loading the actual object on the server and for creating the reference in the first place:

// for load and create reference
public User loadUser() {
    return user.get();
}
public void referenceUser(User user) {
    this.user = Ref.create(user);
}

I hope this solves the problem for everybody. This did not yet go through thorough testing, so comments are still welcome.

I have run a test to compare between using a Key<> and a Ref<> and to me it looks like even with Ref<> the entity is only reconstituted when loadEntity()/.get() is called. So Ref<> if probably better as @Load annotations will work. Maybe the objectify guys can confirm this.

Upvotes: 0

elcid
elcid

Reputation: 574

You need to annotate the property that you want to omit with @ApiResourceProperty(ignored = AnnotationBoolean.TRUE)

Google documentation says the following about the @ApiResourceProperty:

@ApiResourceProperty provides provides more control over how resource properties are exposed in the API. You can use it on a property getter or setter to omit the property from an API resource. You can also use it on the field itself, if the field is private, to expose it in the API. You can also use this annotation to change the name of a property in an API resource.

I encourage you to read more by visiting this link https://developers.google.com/appengine/docs/java/endpoints/annotations#apiresourceproperty

So in your case your class should look like this after the modification.

@Cache
@Entity
public final class Account 
{
    @Id Long id;    
    @Index private Ref<User> user;

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

    @ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
    public User getUser() {
        return user.get();
    }

    @ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
    public void setUser(User user) {
        this.user = Ref.create(user);
    }
}

Upvotes: 0

Related Questions