Reputation: 16245
Using Google App Engine, I've created an endpoint for a Client
class. When I access this using a simple GET statement, it errors out. This happens even though I've only inserted simple data.
I put some the skeleton code of my classes at the end. I'm guessing I need to do some sort of fancy annotations to the line private List<Assessment> assessment
within the Client
class, but I haven't a clue what to do.
Does anyone know how to fix this and if possible, I'd like to know why this error is relevant to the setup because there is no data in the assessment field.
Inserted data
{
"firstName" : "Jon",
"lastName" : "Doe"
}
After this is inserted, I execute this simple statement to select all firstName and lastName fields.
GET http://localhost:8888/_ah/api/clientendpoint/v1/client?fields=items(firstName%2Cid%2ClastName)
This is where I get the error returned
Error
com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException: You have just attempted to access field \"assessment\" yet this field was not detached when you detached the object. Either dont access this field, or detach it when detaching the object. (through reference chain: com.google.api.server.spi.response.CollectionResponse[\"items\"]->com.google.appengine.datanucleus.query.StreamingQueryResult[0]->com.my.app.client.Client[\"assessment\"])
Update: I generated the endpoint via the Eclipse Google API plugin. I've placed this at the end, which corresponds to the GET statement.
I tracked down a bit further and it may just be my lack of understanding on how the returned objects are created.
The error appears to be at
com.my.app.client.Client.jdoGetassessment(Client.java) com.my.app.client.Client.getAssessment(Client.java:247)
but jdoGetAssessment()
doesn't exist in my code so I'm guessing this is generated within Google App Engine. getAssessment()
is defined by.
public List<Assessment> getAssessment() {
return assessment;
}
This seems to trigger the error message, but I still don't understand why that is triggering it with no data.
Client
@Entity
public class Client {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Key id;
private String firstName;
private String lastName;
@ElementCollection
private List<Assessment> assessment;
public Key getId() {
return id;
}
public void setId(Key id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public List<Assessment> getAssessment() {
return assessment;
}
public void setAssessment(List<Assessment> assessment) {
this.assessment = assessment;
}
}
Assessment
@Embeddable
public class Assessment {
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
Generated ClientEndpoint.java
@Api(name = "clientendpoint", namespace = @ApiNamespace(ownerDomain = "myapp.com", ownerName = "myapp.com", packagePath = "mypackage.client"))
public class ClientEndpoint {
/**
* This method lists all the entities inserted in datastore.
* It uses HTTP GET method and paging support.
*`
* @return A CollectionResponse class containing the list of all entities
* persisted and a cursor to the next page.
*/
@SuppressWarnings({ "unchecked", "unused" })
@ApiMethod(name = "listClient")
public CollectionResponse<Client> listClient(
@Nullable @Named("cursor") String cursorString,
@Nullable @Named("limit") Integer limit) {
EntityManager mgr = null;
Cursor cursor = null;
List<Client> execute = null;
try {
mgr = getEntityManager();
Query query = mgr.createQuery("select from Client as Client");
if (cursorString != null && cursorString != "") {
cursor = Cursor.fromWebSafeString(cursorString);
query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
}
if (limit != null) {
query.setFirstResult(0);
query.setMaxResults(limit);
}
execute = (List<Client>) query.getResultList();
cursor = JPACursorHelper.getCursor(execute);
if (cursor != null)
cursorString = cursor.toWebSafeString();
// Tight loop for fetching all entities from datastore and accomodate
// for lazy fetch.
for (Client obj : execute)
;
} finally {
mgr.close();
}
return CollectionResponse.<Client> builder().setItems(execute)
.setNextPageToken(cursorString).build();
}
/**
* This method gets the entity having primary key id. It uses HTTP GET method.
*
* @param id the primary key of the java bean.
* @return The entity with primary key id.
*/
@ApiMethod(name = "getClient")
public Client getClient(@Named("id") Long id) {
EntityManager mgr = getEntityManager();
Client client = null;
try {
client = mgr.find(Client.class, id);
} finally {
mgr.close();
}
return client;
}
/**
* This inserts a new entity into App Engine datastore. If the entity already
* exists in the datastore, an exception is thrown.
* It uses HTTP POST method.
*
* @param client the entity to be inserted.
* @return The inserted entity.
*/
@ApiMethod(name = "insertClient")
public Client insertClient(Client client) {
EntityManager mgr = getEntityManager();
try {
if (containsClient(client)) {
throw new EntityExistsException("Object already exists");
}
mgr.persist(client);
} finally {
mgr.close();
}
return client;
}
/**
* This method is used for updating an existing entity. If the entity does not
* exist in the datastore, an exception is thrown.
* It uses HTTP PUT method.
*
* @param client the entity to be updated.
* @return The updated entity.
*/
@ApiMethod(name = "updateClient")
public Client updateClient(Client client) {
EntityManager mgr = getEntityManager();
try {
if (!containsClient(client)) {
throw new EntityNotFoundException("Object does not exist");
}
mgr.persist(client);
} finally {
mgr.close();
}
return client;
}
/**
* This method removes the entity with primary key id.
* It uses HTTP DELETE method.
*
* @param id the primary key of the entity to be deleted.
* @return The deleted entity.
*/
@ApiMethod(name = "removeClient")
public Client removeClient(@Named("id") Long id) {
EntityManager mgr = getEntityManager();
Client client = null;
try {
client = mgr.find(Client.class, id);
mgr.remove(client);
} finally {
mgr.close();
}
return client;
}
private boolean containsClient(Client client) {
if (client.getId() == null)
return false;
EntityManager mgr = getEntityManager();
boolean contains = true;
try {
Client item = mgr.find(Client.class, client.getId());
if (item == null) {
contains = false;
}
} finally {
mgr.close();
}
return contains;
}
private static EntityManager getEntityManager() {
return EMF.get().createEntityManager();
}
}
Upvotes: 3
Views: 765
Reputation: 9183
The problem is likely JDO lazy loading — you're requesting objects and JDO isn't fetching their attributes until you try and use them, at which point the connection to the datastore is already gone. You can see an example of the code to defeat lazy loading in the auto-generated example:
// Tight loop for fetching all entities from datastore and accomodate
// for lazy fetch.
for (Client obj : execute)
;
This loads all the Client
objects in the list before returning them. However, the List
properties on Client
are also lazily loaded, so you'll need to repeat this process for each Client
object's assessment
attribute.
EDIT: Removed suggestion to use FetchType.EAGER
as it doesn't work on App Engine.
Upvotes: 3