AppEngine / Endpoints - Persist entities in GET (list) call, getting NoPersistenceInformationException

65 views
Skip to first unread message

Adrian Buchmann

unread,
Feb 7, 2016, 10:01:24 AM2/7/16
to Google App Engine
Hi

I'm not sure if it's possible what I'm trying to do. But for our application I also have to gather data from a 3rd-party API and I would like to persist the to the datastore for caching reasons.

To implement the solution I'm using Eclipse with the App Engine plugin and I have created a simple entity like this:

import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;


@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Bird {
 
@PrimaryKey
 
@Persistent
 
long id;
 
@Persistent
 
String name;
 
@Persistent
 
String germanName;
 
@Persistent
 
String englishName;
 
@Persistent
 
String frenchName;
 
@Persistent
 
String italianName;
 
 
public long getId() {
 
return id;
 
}
 
public void setId(long id) {
 
this.id = id;
 
}
 
public String getGermanName() {
 
return germanName;
 
}
 
public void setGermanName(String germanName) {
 
this.germanName = germanName;
 
}
 
public String getEnglishName() {
 
return englishName;
 
}
 
public void setEnglishName(String englishName) {
 
this.englishName = englishName;
 
}
 
public String getFrenchName() {
 
return frenchName;
 
}
 
public void setFrenchName(String frenchName) {
 
this.frenchName = frenchName;
 
}
 
public String getItalianName() {
 
return italianName;
 
}
 
public void setItalianName(String italianName) {
 
this.italianName = italianName;
 
}
 
public String getName() {
 
return name;
 
}
 
public void setName(String name) {
 
this.name = name;
 
}
}


After that I created an Endpoint with the Eclipse plugin and modified listBird slightly (Orange part in the following code), Of course it makes no sense at all to do what I'm doing here, but I just want to give you a clean example what's my problem. Usually I would call a third party API to gather some data to persist there.

package ch.hslu.testproject;

import ch.hslu.testproject.PMF;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.response.CollectionResponse;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.datanucleus.query.JDOCursorHelper;

import java.util.HashMap;
import java.util.List;

import javax.annotation.Nullable;
import javax.inject.Named;
import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;

@Api(name = "birdendpoint", namespace = @ApiNamespace(ownerDomain = "hslu.ch", ownerName = "hslu.ch", packagePath = "testproject") )
public class BirdEndpoint {

/**
* 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 = "listBird")
public CollectionResponse<Bird> listBird(@Nullable @Named("cursor") String cursorString,
@Nullable @Named("limit") Integer limit) {

PersistenceManager mgr = null;
Cursor cursor = null;
List<Bird> execute = null;

try {
mgr = getPersistenceManager();
Query query = mgr.newQuery(Bird.class);
if (cursorString != null && cursorString != "") {
cursor = Cursor.fromWebSafeString(cursorString);
HashMap<String, Object> extensionMap = new HashMap<String, Object>();
extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, cursor);
query.setExtensions(extensionMap);
}

if (limit != null) {
query.setRange(0, limit);
}

execute = (List<Bird>) query.execute();
cursor = JDOCursorHelper.getCursor(execute);
if (cursor != null)
cursorString = cursor.toWebSafeString();

// Tight loop for fetching all entities from datastore and accomodate
// for lazy fetch.
for (Bird obj : execute)
;
// CUSTOM
if(execute.isEmpty()) {
Bird testBird = new Bird() {{
englishName = "Test";
germanName = "Test";
frenchName = "Test";
italianName = "Test";
}};
mgr.makePersistent(testBird);
}
} finally {
mgr.close();
}

return CollectionResponse.<Bird> 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 = "getBird")
public Bird getBird(@Named("id") Long id) {
PersistenceManager mgr = getPersistenceManager();
Bird bird = null;
try {
bird = mgr.getObjectById(Bird.class, id);
} finally {
mgr.close();
}
return bird;
}

/**
* 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 bird the entity to be inserted.
* @return The inserted entity.
*/
@ApiMethod(name = "insertBird")
public Bird insertBird(Bird bird) {
PersistenceManager mgr = getPersistenceManager();
try {
if (containsBird(bird)) {
throw new EntityExistsException("Object already exists");
}
mgr.makePersistent(bird);
} finally {
mgr.close();
}
return bird;
}

/**
* 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 bird the entity to be updated.
* @return The updated entity.
*/
@ApiMethod(name = "updateBird")
public Bird updateBird(Bird bird) {
PersistenceManager mgr = getPersistenceManager();
try {
if (!containsBird(bird)) {
throw new EntityNotFoundException("Object does not exist");
}
mgr.makePersistent(bird);
} finally {
mgr.close();
}
return bird;
}

/**
* 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.
*/
@ApiMethod(name = "removeBird")
public void removeBird(@Named("id") Long id) {
PersistenceManager mgr = getPersistenceManager();
try {
Bird bird = mgr.getObjectById(Bird.class, id);
mgr.deletePersistent(bird);
} finally {
mgr.close();
}
}

private boolean containsBird(Bird bird) {
PersistenceManager mgr = getPersistenceManager();
boolean contains = true;
try {
mgr.getObjectById(Bird.class, bird.getId());
} catch (javax.jdo.JDOObjectNotFoundException ex) {
contains = false;
} finally {
mgr.close();
}
return contains;
}

private static PersistenceManager getPersistenceManager() {
return PMF.get().getPersistenceManager();
}

}

If I call my API now, I get the following response:

{
  "error" : {
    "message" : "org.datanucleus.api.jdo.exceptions.NoPersistenceInformationException: The class \"ch.hslu.testproject.BirdEndpoint$1\" is required to be persistable yet no Meta-Data/Annotations can be found for this class. Please check that the Meta-Data/annotations is defined in a valid file location.\nNestedThrowables:\norg.datanucleus.exceptions.NoPersistenceInformationException: The class \"ch.hslu.testproject.BirdEndpoint$1\" is required to be persistable yet no Meta-Data/Annotations can be found for this class. Please check that the Meta-Data/annotations is defined in a valid file location.",
    "code" : 503,
    "errors" : [ {
      "domain" : "global",
      "reason" : "backendError",
      "message" : "org.datanucleus.api.jdo.exceptions.NoPersistenceInformationException: The class \"ch.hslu.testproject.BirdEndpoint$1\" is required to be persistable yet no Meta-Data/Annotations can be found for this class. Please check that the Meta-Data/annotations is defined in a valid file location.\nNestedThrowables:\norg.datanucleus.exceptions.NoPersistenceInformationException: The class \"ch.hslu.testproject.BirdEndpoint$1\" is required to be persistable yet no Meta-Data/Annotations can be found for this class. Please check that the Meta-Data/annotations is defined in a valid file location."
    } ]
  }
}

Some people (stackoverflow...) with the same issue have started modifing the persistence.xml to get rid of the exception, but I haven't had any success doing so. My persistence.xml is still untouched:

<?xml version="1.0" encoding="UTF-8" ?>
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence

    <persistence-unit name="transactions-optional">
        <provider>org.datanucleus.api.jpa.PersistenceProviderImpl</provider>
        <properties>
            <property name="datanucleus.NontransactionalRead" value="true"/>
            <property name="datanucleus.NontransactionalWrite" value="true"/>
            <property name="datanucleus.ConnectionURL" value="appengine"/>
        </properties>
    </persistence-unit>
</persistence>


Why am I not allowed to persist the testBird-Object?

Kind regards,
Adrian



Adrian Buchmann

unread,
Feb 8, 2016, 9:41:58 AM2/8/16
to Google App Engine
Okay, after a weekend of pain I finally found a solution. I never expected it, but It's because I used object initializers to create my object. It seems like the outcome is different and the generated objects are not persistable...
...
Reply all
Reply to author
Forward
0 new messages