JsonMappingException: Infinite recursion in Cloud Endpoints - google-app-engine

I created an app server using Google Endpoints, it is a backend for an instant messaging app. Every user has a list of friends.
When I create a new friend, I am adding users to each other's friend list using the method below. However, it is giving me following error when I add a friend, because of circular dependency.
I looked at other questions and solutions posted. Most of them are structured differently and they didn't solve my problem.
One answer in this website recommends adding #JSONIgnore, but I don't have any field to add that. I tried to put #JsonManagedReference but I couldn't figure out where to put #JSONBackReference. Other examples on this website, usually have another field that refers to parent, but I don't have it.
Thanks for your help in advance.
Error 500 com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException:
Infinite recursion (StackOverflowError) (through reference chain: com.google.common.collect.TransformingRandomAccessList[0]-
>com.net.myapplication.backend.model.User[\"friends\"]->com.google.common.collect.TransformingRandomAccessList[0]-
>com.net.myapplication.backend.model.User[\"friends\"]-
addFriend method
#ApiMethod(name = "addFriend", httpMethod = "post")
public User addFriend(#Named("regId") String regId, #Named("email") String email) {
User user = findRecord(regId);
User friend = findRecordByEmail(email);
if (user == null){
log.info("User " + regId + " is not registered.");
} else{
if (friend == null){
log.info("User " + email + " is not registered.");
} else{
if (hasFriend(user, friend)){
log.info("User " + email + " is already a friend.");
} else {
user.getFriendsRef().add(Ref.create(friend));
friend.getFriendsRef().add(Ref.create(user));
ofy().save().entity(friend).now();
ofy().save().entity(user).now();
return friend;
}
}
}
return null;
}
User model
#Entity
public class User {
#Id
Long id;
#Index
private String regId;
private String firstName;
private String lastName;
#Index
private String email;
private Language language;
#Load
private List<Ref<User>> friends = new ArrayList<>();
public User() {}
public User(Long id, String regId, String firstName, String lastName, String email, Language language, List<Ref<User>> friends) {
this.id = id;
this.regId = regId;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
this.language = language;
this.friends = friends;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getRegId() {
return regId;
}
public void setRegId(String regId) {
this.regId = regId;
}
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 String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Language getLanguage() {
return language;
}
public void setLanguage(Language language) {
this.language = language;
}
public List<User> getFriends() {
return Deref.deref(friends);
}
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public List<Ref<User>> getFriendsRef(){
return friends;
}
public void setFriends(ArrayList<Ref<User>> friends) {
this.friends = friends;
}
}

It sounds like this question is: "How do I return data structures with cycles through Cloud Endpoints?"
If it serializes data with vanilla Jackson (Jackson 1.x, from the look of that stacktrace), you can't. Jackson 2 has support for JSOG, but that would mean abandoning Cloud Endpoints:
https://github.com/jsog/jsog
https://github.com/jsog/jsog-jackson

Related

Android Room Persistence Library - Many to Many Relation

I am trying to achieve many-to-many relationship in Room Persistence Library. I am trying to create a Notes App with Tags.
The idea is:
A note will have multiple tags.
A tag will have multiple notes.
Show all notes in RecyclerView along with Tags.
To achieve this, I have created two models Note.java, Tag.java and TagJoin model to store the relationship b/w notes and tags. Achieving one-to-one is very easy using #Relation annotation.
Here are my models
#Entity(tableName = "notes")
public class Note {
#PrimaryKey
#NonNull
public final String id;
#ColumnInfo(name = "note")
public String note;
#Ignore
public List<Tag> tags;
#Ignore
public Note(String note) {
this(UUID.randomUUID().toString(), note);
}
public Note(String id, String note) {
this.id = id;
this.note = note;
}
public String getId() {
return id;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public List<Tag> getTags() {
return tags;
}
public void setTags(List<Tag> tags) {
this.tags = tags;
}
#Entity(tableName = "note_tag_join",
primaryKeys = {"noteId", "tagId"},
foreignKeys = {
#ForeignKey(
entity = Note.class,
parentColumns = "id",
childColumns = "noteId",
onDelete = CASCADE),
#ForeignKey(
entity = Tag.class,
parentColumns = "id",
childColumns = "tagId",
onDelete = CASCADE)},
indices = {
#Index(value = "noteId"),
#Index(value = "tagId")
}
)
public static class TagJoin {
#NonNull
public final String noteId;
#NonNull
public final String tagId;
public TagJoin(String noteId, String tagId) {
this.noteId = noteId;
this.tagId = tagId;
}
}
}
Tags Model:
#Entity(tableName = "tags", indices = {#Index(value = "name", unique = true)})
public class Tag {
#PrimaryKey
#NonNull
public String id;
#ColumnInfo(name = "name")
public String name;
#Ignore
public Tag(String name) {
this(UUID.randomUUID().toString(), name);
}
public Tag(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Notes Dao:
#Dao
public interface NoteDao {
#Query("SELECT * FROM notes ORDER BY id DESC")
LiveData<List<Note>> getAllNotes();
#Insert
long insert(Note note);
#Update
void update(Note note);
#Delete
void delete(Note note);
#Query("DELETE FROM notes")
void deleteAll();
#Query("SELECT COUNT(*) FROM notes")
int getNotesCount();
#Query("SELECT notes.* FROM notes\n" +
"INNER JOIN note_tag_join ON notes.id=note_tag_join.noteId\n" +
"WHERE note_tag_join.tagId=:tagId")
List<Note> getAllNotesOfTag(String tagId);
#Insert
void insert(Note.TagJoin... joins);
#Delete
void delete(Note.TagJoin... joins);
}
So far everything is good. Now I want to show the Notes in RecyclerView but I can't find a way to fetch all Notes along with Tags at once. One way is, getting the tags of each note in onBindViewHolder method which I think is wrong as we have to query the db each time row is displayed.
Please provide me suggestions.
PS: I have followed the code provided in this article
https://commonsware.com/AndroidArch/previews/mn-relations-in-room

Azure AD B2C with Graph API - how to get/set user's email?

I add users to Azure AD B2C with Graph API but I don't get it how to store users' email (the primary one). Which field here is the user's primary email address?
As I read here on SO there's no way to populate values in Authentication contact info. It this correct?
Here's how I do it:
public async Task<AdUser> GetUserByObjectId(Guid objectId)
{
string userJson = await SendGraphGetRequest("/users/" + objectId, null);
JObject jUser = JObject.Parse(userJson);
return new AdUser(jUser);
}
internal AdUser(JObject jUser)
{
AccountEnabled = jUser["accountEnabled"].Value<bool>();
CompanyName = jUser["companyName"].Value<string>();
Department = jUser["department"].Value<string>();
DisplayName = jUser["displayName"].Value<string>();
FirstName = jUser["givenName"].Value<string>();
JobTitle = jUser["jobTitle"].Value<string>();
LastName = jUser["surname"].Value<string>();
MailNickname = jUser["mailNickname"].Value<string>();
Mobile = jUser["mobile"].Value<string>();
ObjectId = new Guid(jUser["objectId"].Value<string>());
List<string> mailList = new List<string>(jUser["otherMails"].Count());
mailList.AddRange(jUser["otherMails"].Select(mail => mail.Value<string>()));
OtherMails = mailList.AsReadOnly();
Phone = jUser["telephoneNumber"].Value<string>();
List<(string type, string value)> signInNames = jUser["signInNames"].Select(jToken => (jToken["type"].Value<string>(), jToken["value"].Value<string>())).ToList();
SignInNames = signInNames.AsReadOnly();
UserPrincipalName = jUser["userPrincipalName"].Value<string>();
UserType = jUser["userType"].Value<string>();
}
and here's the Email property of the AdUser:
public string Email
{
get
{
if (SignInNames.Count > 0 && SignInNames[0].type == "emailAddress")
return SignInNames[0].value;
if (OtherMails.Count > 0)
return OtherMails[0];
throw new InvalidOperationException("Don't know where to get user Email");
}
}
You need to make a PATCH request to the users endpoint
{baseurl}/{tenantId}/users?api-version={apiVersion}
Don't forget you access token in the auth header:
Authorization: Bearer {accessToken}
Here's an example model (Java) with methods for calculating and setting the sign-in email on a user object:
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.ArrayList;
import java.util.List;
#JsonIgnoreProperties(ignoreUnknown = true)
public class GraphApiUserExample{
#JsonProperty("objectId")
private String id;
private Boolean accountEnabled;
private PasswordProfile PasswordProfile;
private List<SignInName> signInNames;
private String surname;
private String displayName;
private String givenName;
#JsonProperty("userPrincipalName")
private String userPrincipalName;
public String getId(){
return id;
}
public void setId(final String id){
this.id = id;
}
public Boolean getAccountEnabled(){
return accountEnabled;
}
public void setAccountEnabled(final Boolean accountEnabled){
this.accountEnabled = accountEnabled;
}
public PasswordProfile getPasswordProfile(){
return passwordProfile;
}
public void setPasswordProfile(final PasswordProfile passwordProfile){
this.passwordProfile = passwordProfile;
}
public List<SignInName> getSignInNames(){
return signInNames;
}
public void setSignInNames(final List<SignInName> signInNames){
this.signInNames = signInNames;
}
public String getSurname(){
return surname;
}
public void setSurname(final String surname){
this.surname = surname;
}
public String getDisplayName(){
return displayName;
}
public void setDisplayName(final String displayName){
this.displayName = displayName;
}
public String getGivenName(){
return givenName;
}
public void setGivenName(final String givenName){
this.givenName = givenName;
}
public String getUserPrincipalName(){
return userPrincipalName;
}
public void setUserPrincipalName(final String userPrincipalName){
this.userPrincipalName = userPrincipalName;
}
#JsonIgnore
public String getSignInEmail(){
String email = "";
if(signInNames != null){
for(SignInName signInName : signInNames){
if(signInName.getType().equals("emailAddress")){
email = signInName.getValue();
break;
}
}
}
return email;
}
#JsonIgnore
public void setSignInEmail(String signInEmail){
if(signInNames == null){
signInNames = new ArrayList<>();
signInNames.add(new SignInName("emailAddress", signInEmail));
return;
}
for(SignInName signInName : signInNames){
if(signInName.getType().equals("emailAddress")){
signInName.setValue(signInEmail);
break;
}
}
}
}
SignInName:
public class SignInName {//userName or emailAddress
private String
type,
value;
public String getType(){
return type;
}
public void setType(final String type){
this.type = type;
}
public String getValue(){
return value;
}
public void setValue(final String value){
this.value = value;
}
}
PasswordProfile:
#JsonIgnoreProperties(ignoreUnknown = true)
public class PasswordProfile {
private String password;
private Boolean forceChangePasswordNextLogin;
public String getPassword(){
return password;
}
public void setPassword(final String password){
this.password = password;
}
public Boolean getForceChangePasswordNextLogin(){
return forceChangePasswordNextLogin;
}
public void setForceChangePasswordNextLogin(final Boolean forceChangePasswordNextLogin){
this.forceChangePasswordNextLogin = forceChangePasswordNextLogin;
}
}

How to convert bootstrap datetimepicker string to java datetime

angular controller
$http({
method: 'POST',
url: '/Eatery/save',
contentType:'application/json',
dataType:'json',
data:resvnCtrl.user
})
Spring mvc controller
#RequestMapping(value="/save",method=RequestMethod.POST,consumes=MediaType.APPLICATION_JSON_VALUE,produces=MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public int save(#RequestBody Reservation reservation) {
System.out.println(reservation.getTime());
return reservationRepo.save(reservation);
}
Java model
#Entity
#Table(name="reservations")
public class Reservation implements Serializable{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private int id;
private String cnf;
private String name;
private String email;
private String phone;
#JsonDeserialize(using=CustomJsonDateDeserializer.class)
private LocalDateTime time;
private int seats;
private String note;
public Reservation() { }
public Reservation(String cnf, String name, String email, String phone,
LocalDateTime time, int seats, String note) {
this.cnf = cnf;
this.name = name;
this.email = email;
this.phone = phone;
this.time = time;
this.seats = seats;
this.note = note;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCnf() {
return cnf;
}
public void setCnf(String cnf) {
this.cnf = cnf;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public LocalDateTime getTime() {
return time;
}
public void setTime(LocalDateTime time) {
this.time = time;
}
public int getSeats() {
return seats;
}
public void setSeats(int seats) {
this.seats = seats;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
}
From browser console
email: "kerhb#regerg.e"
name: "kjergk"
note: "wefwef"
phone: "1234567899"
seats: 2
time: "10/23/2015 5:53 PM"
Custom date deserializer
public class CustomJsonDateDeserializer extends JsonDeserializer<Date>
{
#Override
public Date deserialize(JsonParser jsonparser,
DeserializationContext deserializationcontext) throws IOException, JsonProcessingException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
String date = jsonparser.getText();
try {
return format.parse(date);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
}
I have a bootstrap datetimepicker on UI and a java REST webservice at the backend. when i send date select, i got "The request sent by the client was syntactically incorrect.". the datetime string which is sent did not map to the java model. can someone spot my error
#Marged is rigth saying that you didn't cover AM/PM in your date pattern. The proper patter would be yyyy-MM-dd HH:mm a. Note also that you don't need a custom deserializer for this, can rather use #DateTimeFormat
#DateTimeFormat(pattern = "yyyy-MM-dd HH:mm a")
private LocalDateTime time;

Post Spring Data in Restful Controllers with Angular.JS, getting bad request

I'm trying to post a simple model using Restful controllers in Spring Boot, but when I'd like my relationship objects it won't work.
I'm using the latest version of Spring
The Controller Method:
#RequestMapping(value= "rest/person/create", method = RequestMethod.POST)
public ResponseEntity<Person> createOrUpdate(#RequestBody Person person){
System.out.println("creating!");
try{
if (person != null){
peopleRepository.save(person);
}
}
catch (Exception e){
e.printStackTrace();
return new ResponseEntity<Person>(HttpStatus.UNAUTHORIZED);
}
return new ResponseEntity<Person>(HttpStatus.OK);
}
The Person class:
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
#Temporal(TemporalType.DATE)
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date registryDate;
#Temporal(TemporalType.DATE)
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date updateDate;
private String firstName;
private String lastName;
#Temporal(TemporalType.DATE)
#DateTimeFormat(pattern = "dd/MM/yyyy")
private Date birthDate;
private String gender;
private String rg;
private String cpf;
private String phone;
private String celPhone;
#ManyToOne
private Address address;
#Column(unique=true)
private String email;
private String observation;
byte situation;
protected Person(){}
public Person(Long id, Date registryDate, Date updateDate,
String firstName, String lastName, Date birthDate, String gender, String rg,
String cpf, String phone, Address address, String email,
String observation, byte situation) {
super();
this.id = id;
this.registryDate = registryDate;
this.updateDate = updateDate;
this.firstName = firstName;
this.lastName = lastName;
this.birthDate = birthDate;
this.gender = gender;
this.rg = rg;
this.cpf = cpf;
this.phone = phone;
if (address != null)
this.address = address;
this.email = email;
this.observation = observation;
this.situation = situation;
}
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 Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getRegistryDate() {
return registryDate;
}
public void setRegistryDate(Date registryDate) {
this.registryDate = registryDate;
}
public Date getUpdateDate() {
return updateDate;
}
public void setUpdateDate(Date updateDate) {
this.updateDate = updateDate;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getRg() {
return rg;
}
public void setRg(String rg) {
this.rg = rg;
}
public String getCpf() {
return cpf;
}
public void setCpf(String cpf) {
this.cpf = cpf;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getObservation() {
return observation;
}
public void setObservation(String observation) {
this.observation = observation;
}
public byte getSituation() {
return situation;
}
public void setSituation(byte situation) {
this.situation = situation;
}
public String getCelPhone() {
return celPhone;
}
public void setCelPhone(String celPhone) {
this.celPhone = celPhone;
}
}
The JSON Object:
var person = {"firstName":"Joao","lastName":"Silva",
"gender":"Masculino","rg":"808080",
"cpf":"00","phone":"0","celPhone":"0",
"address":{"street":"Rua","number":5,"neighborhood":"Bairro",
"city":{"name":"Cidade",
"state":{"name":"Rio Grande do Sul","uf":"RS"},
"uf":"AE"},"cep":"Cep","complement":"Complemento"},
"email":"maluco#gmail.com","observation":"poisé","situation":0};
And the Angular.JS post ->
$http.post('rest/person/create', person).
success(function(data, status, headers, config) {
console.log('all good!');
$scope.create = true;
}).
error(function(data, status, headers, config) {
console.log('error');
$scope.create = false;
});
I've been looking how to do it, and could'nt find it.
I'm completely beginner with Spring
The error happens only with the relationships, if I remove
"address":{"street":"Rua","number":5,"neighborhood":"Bairro",
"city":{"name":"Cidade",
"state":{"name":"Rio Grande do Sul","uf":"RS"},
"uf":"AE"},"cep":"Cep","complement":"Complemento"}
It will work.
You need to declare Content-Type when you post a request, and then #RequesetBody annotation convert http body text to a JSON object using jackson.
So you need to change your content-types, and stringify your json object.
$http({
url: 'rest/person/create',
dataType: 'json',
method: 'POST',
data: JSON.stringify(persion),
headers: {
"Content-Type": "application/json"
}
}).success(function(response){
$scope.response = response;
}).error(function(error){
$scope.error = error;
});

JAVA Google App Engine + Facebook API + GSON = Trouble with Javabean

I am trying to get the user's friends list from Facebook.
The problem seems to be the Javabean...
FBUser fbuser = new Gson().fromJson(jsonStr, FBUser.class);
public class FBUser implements Serializable {
private static final long serialVersionUID = -3154429420153433117L;
private String id;
private String name;
private String email;
private Friends friendsList = new Friends();
private FBUser() { }
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<Data> getFriendsList() {
return friendsList.getData();
}
public static class Friends implements Serializable {
private static final long serialVersionUID = 6991758772193514527L;
private List<Data> data;
private Friends() { }
public List<Data> getData() {
return data;
}
public void setData(List<Data> data) {
this.data = data;
}
public class Paging implements Serializable {
private static final long serialVersionUID = 1689816298710621080L;
private String next;
private Paging() { }
public String getNext() {
return next;
}
public void setNext(String next) {
this.next = next;
}
}
}
public class Data implements Serializable {
private static final long serialVersionUID = -5008541658519841090L;
private String id;
private String name;
private Data() { }
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
Json:
json: {"id":"10861234","name":"Whatever","email":"whatever\u0040gmail.com","friends":{"data":[{"name":"Someone","id":"10861234"},{"name" ...43"}],"paging":{"next":"https:\/\/graph.facebook.com\/10861234\/friends..."}}}
The fields ID, Name and Email I can retrieve succesfully... but the friendsList is null... =(
Maybe it is the way I am trying to get it from the nested class, any suggestions on that?
There is no friendsList in your JSON (or, there's no friends in your Java class - whichever way you'd like to look at it). Gson silently ignores anything in the JSON that is not present in your classes.
You have a field friends whose value is an object. That object has a field data which is an array of objects and a field paging which is another object.
You need to write Java classes that match that structure. You're ... close.
In your FBUser class change:
private Friends friendsList = new Friends();
to:
private Friends friends = new Friends();
or:
#SerializedName("friends")
private Friends friendsList = new Friends();
Then in your Friends class you need to add:
private Paging paging = new Paging();
Also note that you don't have to initialize these values unless you specifically don't want them to be non-null when using these classes elsewhere.

Resources