I am trying to create an android application which interact's with Google App Engine to load and save entities in google datastore. I think I have a correct configuration, but I don't know if I can upload my application to test it, it just hangs forever and then gives me this error:
SEVERE: Endpoints configuration not updated. The app returned an error when the Google Cloud Endpoints server attempted to communicate with it.
However, if I change the version number then the new version does appear in my console online even after getting this message. I have read all of Google's documentation on Datastore and couldn't figure out what was wrong. I have been having this issue for a month and haven't been able to find a solution. I read somewhere that if the Application contains an error it won't be uploaded so I will include all of my code. I also don't really know what I am doing an am not even sure if I am on the right track for why this isn't working, so if you notice anything stupid, you should please tell me. Thank you in advance, you will be my hero and my god if you are able to fix this for me.
Here is my endpoint:
package com.polo.backend.endpoints;
/**
* Created by sagesmith on 4/16/16.
*
* Is used to interact with the server
*/
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.ConflictException;
import com.google.api.server.spi.response.NotFoundException;
import com.googlecode.objectify.Ref;
import com.polo.backend.services.ObjcfyService;
import com.polo.backend.entities.Event;
import com.polo.backend.entities.User;
import java.util.List;
import javax.inject.Named;
/**
*
*/
@Api(
name = "userEndpoint",
version = "v1",
namespace = @ApiNamespace(
ownerDomain = "backend.polo.com",
ownerName = "backend.polo.com",
packagePath = ""
)
)
public class UserEndpoint {
/**
* Registers the two entities used in this class
*/
public UserEndpoint(){
ObjcfyService.register(User.class);
ObjcfyService.register(Event.class);
}
@ApiMethod(name = "insertUser")
/**
* Inserts a new {@code User} into the data store
*/
public void insertUser(User user) throws ConflictException, NotFoundException{
if(getUser(user.getUsername()) != null){
throw new ConflictException("Object already exists");
}
}
/**
*
* @param user
* @throws NotFoundException
*/
@ApiMethod(name = "updateUser")
public void updateUser(User user) throws NotFoundException{
if(getUser(user.getUsername()) != null) {
ObjcfyService.objectify().save().entity(user).now();
}
}
/**
*
* @param username
* @throws NotFoundException
*/
@ApiMethod(name = "removeUser")
public void removeUser(@Named("username") String username) throws NotFoundException{
User record = getUser(username);
if(record != null){
ObjcfyService.objectify().delete().entity(record).now();
}
}
/**
*
* @param username
* @return
* @throws NotFoundException
*/
@ApiMethod(name = "getUser")
public User getUser(@Named("username") String username) throws NotFoundException{
User user = ObjcfyService.objectify().load().type(User.class).id(username).now();
if(user != null) {
List<Ref<User>> friendRefs = user.getFriendRefs();
List<Ref<Event>> repeatingEventRefs = user.getRepeatingEventRefs();
List<Ref<Event>> nonRepeatingEventRefs = user.getNonRepeatingEventRefs();
for (Ref<User> friendRef : friendRefs) {
user.addFriend(friendRef.get());
}
for (Ref<Event> repeatingEventRef : repeatingEventRefs) {
user.addRepeatingEvent(repeatingEventRef.get());
}
for (Ref<Event> nonRepeatingEventRef : nonRepeatingEventRefs) {
user.addNonRepeatingEvent(nonRepeatingEventRef.get());
}
} else{
throw new NotFoundException("User not found");
}
return user;
}
}
Here is my User entity:
package com.polo.backend.entities;
/**
* Created by sagesmith on 4/16/16.
*/
import com.google.api.server.spi.config.AnnotationBoolean;
import com.google.api.server.spi.config.ApiResourceProperty;
import com.googlecode.objectify.Ref;
import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;
import com.googlecode.objectify.annotation.Index;
import com.googlecode.objectify.annotation.Load;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nullable;
@Entity
public class User {
@Id private String username = null;
@Index private String firstName = null;
@Index private String lastName = null;
@Nullable private String status = null;
@Nullable private String location = null;
@Load @Nullable private List<Ref<User>> friendRefs = new ArrayList<>();
@Load @Nullable private List<Ref<Event>> nonRepeatingEventRefs = new ArrayList<>();
@Load @Nullable private List<Ref<Event>> repeatingEventRefs = new ArrayList<>();
@Nullable public List<User> friends = new ArrayList<>();
@Nullable public List<Event> nonRepeatingEvents = new ArrayList<>();
@Nullable public List<Event> repeatingEvents = new ArrayList<>();
/**
*Returns the first name from the {@code User}
*
* @return the first name of the {@code User} as a {@code String}
*/
public String getFirstName(){
return firstName;
}
/**
* Returns the last name from the {@code User}
*
* @return the last name of the {@code User} as a {@code String}
*/
public String getLastName(){
return lastName;
}
/**
* Return the username from the user
*
* @return the username of the user as a String
*/
public String getUsername(){
return username;
}
/**
* Sets the username of the {@code User}
*
* @param username Username to set the {@code User}'s username to
*/
public void setUsername(String username){
this.username = username;
}
/**
* Sets the firstName of the {@code User}
*
* @param firstName First name to set the {@code User}'s first name to
*/
public void setFirstName(String firstName){
this.firstName = firstName;
}
/**
* Sets the lastName of the {@code User}
*
* @param lastName Last name to set the {@code User}'s last name to
*/
public void setLastName(String lastName){
this.lastName = lastName;
}
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
/**
* Adds a friend to the {@code User}'s list of friends
*
* @param user User to add
*/
public void addFriendRef(User user){
friendRefs.add(Ref.create(user));
}
/**
* Returns the {@code User}'s friends
*
* @return {@code List<Users>} the friends of the {@code User}
*/
public List<User> getFriends(){
return friends;
}
/**
* Adds a friend to the users {@code List} of friends
*
* @param user {@code User} to add as a friend
*/
public void addFriend(User user){
friends.add(user);
}
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
/**
* Returns a {@code List<Ref<User>>} of references to the {@code User}'s friends
*
* @return {@code List<Ref<User>>}
*/
public List<Ref<User>> getFriendRefs(){
return friendRefs;
}
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
/**
* Returns a {@code List<Ref<Event>>} of references to the {@code User}'s repeating
* {@code Event}s
*
* @return {@code List<Ref<Event>>}
*/
public List<Ref<Event>> getRepeatingEventRefs(){
return repeatingEventRefs;
}
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
/**
* Returns a {@code List<Ref<Event>>} of references to the {@code User}'s non repeating
* {@code Event}s
*
* @return {@code List<Ref<Event>>}
*/
public List<Ref<Event>> getNonRepeatingEventRefs(){
return nonRepeatingEventRefs;
}
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
/**
* Sets the {@code User}'s status to a given {@code String}
*
* @param status {@code String} to set an {@code Users}'s status to
*/
public void setStatus(String status){
this.status = status;
}
/**
* Returns the {@code User}'s status as a {@code String}
*
* @return {@code String} status
*/
public String getStatus(){
return status;
}
/**
* Returns the {@code User}'s location as a {@code String}
*
* @return {@code String} location
*/
public String getLocation(){
return location;
}
/**
* Sets the {@code User}'s location to a given {@code String}
*
* @param location {@code String} to set an {@code Users}'s location to
*/
public void setLocation(String location){
this.location = location;
}
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
/**
* Adds a repeating {@code Event} to the {@code User}'s schedule
*
* @param event {@code Event} to add to the {@code User}'s schedule
*/
public void addRepeatingEvent(Event event){
repeatingEventRefs.add(Ref.create(event));
}
@ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
/**
* Adds a non repeating {@code Event} to the user's schedule
*
* @param event Event to add to the the user's schedule
*/
public void addNonRepeatingEvent(Event event){
nonRepeatingEventRefs.add(Ref.create(event));
}
/**
* Returns a {@code List<Event>} of repeating {@code Event}s from the user's schedule
*
* @return List<Event> repeatingEvents from user's schedule
*/
public List<Event> getRepeatingEvents(){
return repeatingEvents;
}
/**
* Returns a list of nonRepeatingEvents from the user's schedule
*
* @return List<Event> repeatingEvents from user's schedule
*/
public List<Event> getNonRepeatingEvents(){
return nonRepeatingEvents;
}
}
and my Event entity:
package com.polo.backend.entities;
import com.googlecode.objectify.annotation.Entity;
/**
* Created by sagesmith on 5/3/16.
*/
@Entity
public class Event {
private Integer startIndex;
private Integer endIndex;
private Byte[] color;
public Event(Integer startIndex, Integer endIndex, Byte red, Byte green, Byte blue){
this.startIndex = startIndex;
this.endIndex = endIndex;
color = new Byte[]{red, green, blue};
}
}
and my ObjectifyService:
package com.polo.backend.services;
/**
* Created by sagesmith on 4/16/16.
*/
import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.ObjectifyService;
public class ObjcfyService {
public static void register(Class<?> eClass){
ObjectifyService.register(eClass);
}
public static Objectify objectify(){
return ObjectifyService.ofy();
}
public static ObjectifyFactory objectifyFactory(){
return ObjectifyService.factory();
}
}
Here is my appengine-web.xml, I've replaced my appid with my_id for obvious reasons:
<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>my_id</application>
<version>1</version>
<threadsafe>true</threadsafe>
<system-properties>
<property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
</system-properties>
Here is my web.xml
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<servlet>
<servlet-name>SystemServiceServlet</servlet-name>
<servlet-class>com.google.api.server.spi.SystemServiceServlet</servlet-class>
<init-param>
<param-name>services</param-name>
<param-value>com.polo.backend.endpoints.UserEndpoint</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>SystemServiceServlet</servlet-name>
<url-pattern>/_ah/spi/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
Finally here is the AsyncTask I use to communicate with datastore from my Android application, I have replaced my Application ID with my_id again:
package com.polo.client;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;
import com.google.api.client.extensions.android.http.AndroidHttp;
import com.google.api.client.extensions.android.json.AndroidJsonFactory;
import com.polo.StaticAssets;
import com.polo.activity.Home;
import com.polo.backend.userEndpoint.UserEndpoint;
import com.polo.backend.userEndpoint.model.User;
import java.io.IOException;
/**
* Created by sagesmith on 4/17/16.
*/
public class UserAsyncTask extends AsyncTask<Void, Void, User>{
private static UserEndpoint userApiService = null;
private Home context;
private String username;
/**
*
* @param context
*/
public UserAsyncTask(Home context){
this.context = context;
username = StaticAssets.getUserName(context);
}
/**
*
* @param params
* @return
*/
@Override
protected User doInBackground(Void... params){
if(userApiService == null){
UserEndpoint.Builder builder = new UserEndpoint.Builder(
AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(),
null
)
.setRootUrl("https://my_id.appspot.com/_ah/api/");
userApiService = builder.build();
}
User user = null;
try{
user = userApiService.getUser(username).execute();
} catch (IOException e){
Log.e("User not found", e.getMessage());
}
return user;
}
/**
*
* @param result
*/
@Override
protected void onPostExecute(User result) {
if(result!=null)
Toast.makeText(context, result.getFirstName(), Toast.LENGTH_LONG).show();
}
}
It helps to take a look at the full error message which advises:
See the deployment troubleshooting documentation for more information: https://developers.google.com/appengine/docs/java/endpoints/test_deploy#troubleshooting_a_deployment_failure
The docs advise to check your logs for '/_ah/spi/BackendService.getApiConfigs' as a first step. If I try to deploy an Endpoints backend based on the sample I see the following in my logs:
javax.servlet.ServletContext log: unavailable
java.lang.reflect.InvocationTargetException
at com.google.appengine.runtime.Request.process-b693af604777a85a(Request.java)
...
Caused by: java.lang.StackOverflowError
...
com.googlecode.objectify.impl.translate.ClassTranslatorFactory.create(ClassTranslatorFactory.java:49)
...
This error only happens on calls to ObjectifyService.register(class)
, hmm...
There's something in your User
class which would cause infinite recursion when Objectify tries to register the class and result in a StackOverflowError:
@Nullable public List<User> friends = new ArrayList<>();
Rather than defining a list of itself, you can fix this by defining a list of Ref<User>
as is done elsewhere in the class (maybe the above was a typo):
@Nullable public List<Ref<User>> friends = new ArrayList<>();
Also, another issue is caused by the Event
class missing an @Id, which can
fixed thus:
@Entity
public class Event {
@Id private Long id;
...
After making these changes, I can deploy the Endpoints app without any errors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With