Neo4j RelationshipEntities and cyclic JSON serialization issue with Spring Data Rest - spring-data-neo4j

I'm having an issue with serializing #RelationshipEntities to JSON via Spring Data Rest. Whenever I create the #RelationshipEntity, I run into an infinite recursion on serializing the graph to JSON.
Using JSOG to try to render the graph only results in a different, malformed JSON response.
While I can avoid the issue by using #JsonManagedReference, it doesn't solve the problem as I would like to expose the relationship from both nodes.
I've created a simple application that exhibits the issue. It can be found here: https://github.com/cyclomaniac/neo4j-spring-data-rest-cyclic
It implements very basic Team and Player NodeEntities with a single RelationshipEntity, PlayerPosition.
Player:
#NodeEntity
#JsonIdentityInfo(generator= JSOGGenerator.class)
public class Player {
#GraphId
#JsonProperty("id")
private Long id;
private String name;
private String number;
#Relationship(type = "PLAYS_ON")
private PlayerPosition position;
...
Team:
#NodeEntity
#JsonIdentityInfo(generator= JSOGGenerator.class)
public class Team {
#GraphId
#JsonProperty("id")
private Long id;
private String name;
#Relationship(type = "PLAYS_ON", direction = Relationship.INCOMING)
Set<PlayerPosition> teamPlayers;
...
PlayerPosition:
#RelationshipEntity(type="PLAYS_ON")
#JsonIdentityInfo(generator= JSOGGenerator.class)
public class PlayerPosition {
#GraphId
#JsonProperty("id")
private Long id;
private String position;
#StartNode
private Player player;
#EndNode
private Team team;
...
When wired up to a GraphRepository, hitting the /teams endpoint results in the following output with JSOG in place:
{
"_embedded" : {
"teams" : [ {
"#id" : "1",
"name" : "Cubs",
"teamPlayers" : [ {
"#id" : "2",
"position" : "Catcher",
"player" : {
"#id" : "3"
Notice that the JSON ends prematurely. The server throws an exception:
2016-11-04 15:48:03.495 WARN 12287 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to write HTTP message:
org.springframework.http.converter.HttpMessageNotWritableException:
Could not write content: Can not start an object,
expecting field name; nested exception is
com.fasterxml.jackson.core.JsonGenerationException:
Can not start an object, expecting field name
My assumption is that I've chosen a poor way to implement the relationship, though it feels fairly straightforward. I'd appreciate any tips on how I can properly expose this relationship, ideally via Spring Data Rest, from both the Team and Player nodes.

Try to annotate with #JsonIgnore or pair: #JsonBackReference and #JsonManagedReference

Related

Unable to make general purpose query, without Domain entity

I am new to springbootneo4j. I have difficulties making general purpose queries. I want to be able to make any kind of query and get result without domain entity.
I am making a query like this in repository class:
#Query("MATCH (p:Employee) RETURN ID(p) as id, p.name as name, p.salary as salary ")
that is not working, but the following query is working:
#Query("MATCH (p:Employee) RETURN p ")
My domain entity class is something like this:
#NodeEntity
public class Employee {
#Id
#GeneratedValue
private Long id;
private String name;
private int salary;
#Relationship(type = "IS_BOSSOF", direction = Relationship.UNDIRECTED) Set<Employee> reporties = new HashSet<>();
public Employee() {}
// some more code
}
Create a command is like this:
(laksmi:Employee{name:"Laksmi",salary:200}),(ashwini:Employee{name:"AshwiniV",salary:300}), (harish:Employee{name:"Harish",salary:400}), (jay)-[:IS_BOSSOF]->(mukesh), (xyz)-[:IS_BOSSOF]->(mukesh), (harish)-[:IS_BOSSOF]->(ashwini),
Whenever you are distributing properties you need to use #QueryResult annotation on your class
SDN

Designing Model with foreign key

I am building an ORM by using Unit or Work and Repository using Dapper. I have searched the internet on this problem and no luck.
I have the following tables:
As you can see, Instance has Entity inside. I have 2 approaches:
Approach 1:
public class Entity
{
public int Id {get;set;}
public string Name {get;set;}
}
public class Instance
{
public int Id {get;set;}
public Entity Entity {get;set;}
public string Name {get;set;}
}
How can I get value for Entity with this approach?
Approach 2 (according to this link):
public class Entity
{
public int Id {get;set;}
public string Name {get;set;}
}
public class Instance
{
public int Id {get;set;}
public int EntityId {get;set;}
public string Name {get;set;}
}
Which design is better for use?
You can use QueryMultiple if you want to fetch the data from two different tables and fill it up in two different POCO classes. Following is copied from here:
string sql = "SELECT * FROM Invoice WHERE InvoiceID = #InvoiceID; SELECT * FROM InvoiceItem WHERE InvoiceID = #InvoiceID;";
using (var connection = My.ConnectionFactory())
{
connection.Open();
using (var multi = connection.QueryMultiple(sql, new {InvoiceID = 1}))
{
var invoice = multi.Read<Invoice>().First();
var invoiceItems = multi.Read<InvoiceItem>().ToList();
}
}
Both the models you mentioned in your code can be handled with this approach.
As an alternative approach, you can combine your two POCOs in one or you can use inheritance as well. But, looking at your data model, I do not think this is applicable to this particular case.
Which design is better for use?
Up to you. Whatever suits your project needs keeping down the unnecessary complexities is good for you.

Enum conversion to String not working on #Indexed unique field

I'm a Neo4j/Spring-data newbie so apologies if this is something obvious but I looked here and there and can't quite figure out if it's a bug or feature.
I'm using SDN 3.1.0 and Neo4j 2.0.4, running in memory for now (for testing).
I have a super simple POJO that I try to save into Neo4j using SDN. It looks like so:
#NodeEntity
public class Weekday {
#GraphId
private Long id;
#Indexed(unique = true)
public DayOfWeek weekdayCode;
}
Everything works beautifully when I make it non-uniquely indexed, or not indexed at all. It works fine with unique constraint when I make it a String as well. (Well, sort of, I'm aware that it doesn't throw an exception but silently updates existing one - this is not perfect but I found JIRA issue related to that). Unfortunately the moment I try to save it as enum with unique constraint I get an exception:
org.springframework.dao.InvalidDataAccessResourceUsageException: Error executing statement MERGE (n:`Weekday` {`weekdayCode`: {value}}) ON CREATE SET n={props} return n; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: Error executing statement MERGE (n:`Weekday` {`weekdayCode`: {value}}) ON CREATE SET n={props} return n; nested exception is java.lang.IllegalArgumentException: [MONDAY:java.time.DayOfWeek] is not a supported property value
at org.springframework.data.neo4j.support.query.CypherQueryEngineImpl.query(CypherQueryEngineImpl.java:61)
at org.springframework.data.neo4j.support.schema.SchemaIndexProvider.merge(SchemaIndexProvider.java:114)
at [...]
Caused by: org.springframework.dao.InvalidDataAccessResourceUsageException: Error executing statement MERGE (n:`Weekday` {`weekdayCode`: {value}}) ON CREATE SET n={props} return n; nested exception is java.lang.IllegalArgumentException: [MONDAY:java.time.DayOfWeek] is not a supported property value
at org.springframework.data.neo4j.support.query.CypherQueryEngineImpl.parseAndExecuteQuery(CypherQueryEngineImpl.java:72)
at org.springframework.data.neo4j.support.query.CypherQueryEngineImpl.query(CypherQueryEngineImpl.java:58)
... 63 more
Caused by: java.lang.IllegalArgumentException: [MONDAY:java.time.DayOfWeek] is not a supported property value
at org.neo4j.kernel.api.properties.PropertyConversion.convertProperty(PropertyConversion.java:107)
at org.neo4j.kernel.api.properties.Property.property(Property.java:51)
at [...]
This, as far as I can see, is because the unique field is put into a map "props", and the contents of the map is not automatically converted so it sends an enum to Neo4j, which obviously it doesn't like.
Is this expected or should I raise a bug with SDN?
If that's expected behaviour, do I have any alternatives other than making the field a String?
I'm not sure if it is a real bug, however I had a similar problem using my own Enum class.
So, you could try something like this:
Create the converters
Register them in neo4j
First create your converters like:
#Component
public class StringToDayOfWeekConverter implements Converter<String, DayOfWeek> {
#Override
public DayOfWeek convert(String source) {
return DayOfWeek.valueOf(source);
}
}
#Component
public class DayOfWeekToStringConverter implements Converter<DayOfWeek, String> {
#Override
public String convert(DayOfWeek source) {
return source.name();
}
}
Then you register the converters, so neo4j can use them:
#Configuration
#EnableNeo4jRepositories("my.repository.package")
#EnableTransactionManagement
public class MyNeo4jConfiguration extends Neo4jConfiguration {
#Autowired
private StringToDayOfWeekConverter stringToDayOfWeekConverter;
#Autowired
private DayOfWeekToStringConverter dayOfWeekToStringConverter;
#Override
protected ConversionService neo4jConversionService() throws Exception {
ConverterRegistry converterRegistry = (ConverterRegistry) super.neo4jConversionService();
converterRegistry.addConverter(stringToDayOfWeekConverter);
converterRegistry.addConverter(dayOfWeekToStringConverter);
return (ConversionService) converterRegistry;
}
}
create an converter extends EnumStringConverter such like this:
package com.noopu.pyramid.domain.model.converter;
import com..pyramid.common.UserType;
import org.neo4j.ogm.typeconversion.EnumStringConverter;
public class UserTypeStringConverter extends EnumStringConverter {
public UserTypeStringConverter ( ) {
super ( UserType.class );
}
}
and used #Converter annotation like under:
package com.noopu.pyramid.domain.model;
import com.noopu.pyramid.common.UserType;
import com.noopu.pyramid.domain.model.converter.UserTypeStringConverter;
import lombok.Data;
import org.neo4j.ogm.annotation.GraphId;
import org.neo4j.ogm.annotation.Index;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.typeconversion.Convert;
import java.io.Serializable;
#NodeEntity
#Data
public class User implements Serializable {
private static final long serialVersionUID = 8979348201709416439L;
#GraphId
private Long graphId;
#Index
protected Long id;
#Index(unique = true)
private Long uid;
#Index(unique = true)
private String phone;
#Index
#Convert(UserTypeStringConverter.class)
private UserType type;
}

ConstraintViolationException in Java RESTful Webservice

I'm relatively new to JavaEE and web services, however, I'm using netbeans to generate my client and webservice resources. I have a resource "CustomerData" that represents a mysql database table and a value "rewardsPoints" representing a column in that table, however, I am unable to update the value due to a ConstraintViolationException, specifically:
javax.validation.ConstraintViolationException: Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'preUpdate'. Please refer to embedded ConstraintViolations for details.
I'm not familiar with the 'preUpdate' callback event, is it something I need to override? I can't seem to figure out exactly why this exception is being thrown, but, as I said, i'm very new to web service programming. Here are my classes:
#Stateless
#Path("customers")
public class CustomerDataFacadeREST extends AbstractFacade<CustomerData> {
#PersistenceContext(unitName = "CustomerPortalPU")
private EntityManager em;
public CustomerDataFacadeREST() {
super(CustomerData.class);
}
#PUT
#Path("{id}")
#Consumes({"application/xml", "application/json"})
public void edit(#PathParam("id") Integer id, CustomerData entity) {
super.edit(entity);
}
#GET
#Path("{id}")
#Produces({"application/xml", "application/json"})
public CustomerData find(#PathParam("id") Integer id) {
return super.find(id);
}
#GET
#Path("addPoints/{id}/{amount}")
#Produces({"text/plain"})
public String addPoints(#PathParam("id") Integer id, #PathParam("amount") int amount) {
CustomerData customer = find(id);
customer.getRewardsPoints(customer.getRewardsPoints() + amount);
em.persist(customer);
edit(customer);
return customer.getRewardsPoints();
}
#Override
protected EntityManager getEntityManager() {
return em;
}
}
And the CustomerData entity class:
#Entity
#Table(name = "tbl_customer_data")
#XmlRootElement
public class CustomerData implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#NotNull
#Column(name = "rewards_points")
private int rewardsPoints;
public CustomerData(Integer id, int rewardsPoints) {
this.id = id;
this.rewardsPoints = rewardsPoints;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public int getRewardsPoints() {
return rewardsPoints;
}
public void setRewardsPoints(int rewardsPoints) {
this.rewardsPoints = rewardsPoints;
}
}
When I try to access the URI:
http://localhost:8080/CustomerPortal/ws/customers/addPoints/1/5
to add 5 points to user with id 1 i get an HTTP 500 error and in the glassfish logs it says
[2013-11-05T03:28:11.733-0500] [glassfish 4.0] [WARNING] [ejb.system_exception] [javax.enterprise.system.container.ejb.com.sun.ejb.containers] [tid: _ThreadID=21 _ThreadName=http-listener-1(3)] [timeMillis: 1383640091733] [levelValue: 900] [[
EJB5184:A system exception occurred during an invocation on EJB CustomerDataFacadeREST, method: public java.lang.String com.webservice.entities.CustomerDataFacadeREST.addPoints(java.lang.Integer,int)]]
[2013-11-05T03:28:11.741-0500] [glassfish 4.0] [WARNING] [] [javax.enterprise.web] [tid: _ThreadID=21 _ThreadName=http-listener-1(3)] [timeMillis: 1383640091741] [levelValue: 900] [[
StandardWrapperValve[com.webservice.entities.ApplicationConfig]: Servlet.service() for servlet com.webservice.entities.ApplicationConfig threw exception
javax.validation.ConstraintViolationException: Bean Validation constraint(s) violated while executing Automatic Bean Validation on callback event:'preUpdate'. Please refer to embedded ConstraintViolations for details.
Any resources, insight or information regarding this issue would be extremely helpful.
The exception has little to do with web services: it has to do with Bean Validation instead.
In this case, since the Validation fails inside method addPoints (look at the stack trace) the only line that can cause it is when persisting or editing an Entity of type CustomerData. The only constraint you have in that class is that rewardsPoints should not be null. So, that's the cause of the exception.
However there some things that won't work in addPoints method:
You should check that find() method doesn't return a null object.
customer.getRewardsPoints(customer.getRewardsPoints() + amount) never sets the property (does it compile?)
EntityManager.persist() throws exception if the entity already exists. You probably want to remove that line if you only want to edit (update) the entity.
Note: I am not sure that the code you have posted is really compiling and causing that exception. That's probably caused by another version.

Trouble with my first RESTful Web Services

i'm developing my firts RestFul webServices in javaEE6.
This is my Entity Bean
#XmlRootElement
#Entity
public class MyEntity implements Serializable {
#Id
#GeneratedValue
private long idEntity;
private String name;
private String description;
#OneToMany(mappedBy = "entity" , fetch = FetchType.EAGER)
private List<EntityB> list;
//Get and set
}
#Entity
public class EntityB {
#Id
#GeneratedValue
private long idCategoria;
#ManyToOne
private MyEntity myEntity;
}
this is my webServices
#Path("myentity")
#Produces( {MediaType.APPLICATION_XML , MediaType.APPLICATION_JSON })
#Consumes( {MediaType.APPLICATION_XML , MediaType.APPLICATION_JSON })
#Stateless
public class MyEntityService {
#Inject
MyEntityDao entityDao;
#GET
#Path("{id}/")
public MyEntity findById(#PathParam("id") Long id){
return entityDao.findById(id);
}
}
Finally i configured Jersey
#ApplicationPath("ws")
public class ApplicationConfig extends Application {
}
Now , if i try a invoke my web services (localhost:8080/xxxx/ws/myentity) i get this error:
HTTP Status 500 - javax.xml.bind.MarshalException - with linked exception: [com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML
You have a cyclic Graph of objects, which is not allowed , as it would result in an "infinite" XML.
MyEntity Holds a reference to EntityB , which holds a reference that goes back to MyEntity.
The marshaller will try to marshall MyEntity > EntityB > MyEntity > EntityB and so on.
You can mark MyEntity in EntityB as #XmlTransient, to avoid this.
However, It might not be a good idea to try to use the same Classes of objects across all your project (From persistence layers to communication layers).