6
votes

I have an entity, with a field containing a list to Refs to other entities (always 4). I'm trying to get some entities, and dispatch them for a jsp for display. I want all the Refs in the field to be loaded as well, and to access them in the jsp.

Here is my basic structure:

@Entity
public class Question {
    @Id Long id;
    @Index String question;
    @Load List<Ref<Answer>> answers = new ArrayList<Ref<Answer>>();
}

When I'm fetching question like this, obviously there's an error in the jsp. Makes sense, because answers field is not a list of answers, but of refs:

ObjectifyService.register(Question.class);
ObjectifyService.register(Answer.class);

List<Question> questions = ofy().load().type(Question.class).limit(50).list();

req.setAttribute("questions", questions);
try { 
    getServletContext().getRequestDispatcher("/admin/view-questions.jsp").forward(req, resp); 
} catch (ServletException e) {
    System.out.println (e.getMessage());
}

So how do access the answers in the jsp? is the only way to manually loop through the questions and do a get() for the answers field?

3

3 Answers

8
votes

You might find this handy:

public class Deref {
    public static class Func<T> implements Function<Ref<T>, T> {
        public static Func<Object> INSTANCE = new Func<Object>();

        @Override
        public T apply(Ref<T> ref) {
            return deref(ref);
        }
    }

    public static <T> T deref(Ref<T> ref) {
        return ref == null ? null : ref.get();
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static <T> List<T> deref(List<Ref<T>> reflist) {
        return Lists.transform(reflist, (Func)Func.INSTANCE);
    }
}

Using this, you can provide a method like:

@Entity
public class Question {
    @Id Long id;
    @Index String question;
    @Load List<Ref<Answer>> answers = new ArrayList<Ref<Answer>>();
    public List<Answer> getAnswers() { return Deref.deref(answers); }
}

One caveat is that it's a readonly list. With a little creativity you can create a writeable list, but it requires creating a ReversibleFunction and a new List.transform() method. If you just want something simple for JSPs, you might not want to worry about it.

Alternatively, you can use the 'value' property of a Ref in most expression languages.

8
votes

How about something like this:

@Entity
public class Question {
    @Id Long id;
    @Index String question;
    @Load List<Ref<Answer>> answers = new ArrayList<Ref<Answer>>();

    public List<Answer> loadAnswers()
    {
        return new ArrayList<Answer>(ofy().load().refs(answers).values());
    }
}

Since you've already loaded the entities using the @Load annotation, I think objectify will just pull them from the session cache (they won't be loaded 2x).

And if you weren't using the @Load annotation then this method would perform a batch load which is probably better performance than looping through the Refs and calling get() individually.

-1
votes

What happens if you change the List type?

@Entity
public class Question {
    @Id Long id;
    @Index String question;
    @Load List<Answer> answers = new ArrayList<Answer>();
}

I check it and all goes well, but in the documentation (https://code.google.com/p/objectify-appengine/wiki/Entities#Ref_s) there are not any reference to this case.

Is it safe to do it?