SDN 2.1.0/neo4j 1.8: java.lang.IllegalArgumentException: Cannot obtain single field value for field 'schoolRef' - spring-data-neo4j

I am able add and save multiple SchoolRef, but am getting the error after retrieving the (ancestor and eagerly fetching the) Education object and then attempting to add another SchoolRef. This was working with SDN 2.0.1, but I've also changed other things, including the Repository/Cypher query below, so I can't isolate it to the upgrade.
#Fetch #RelatedTo(type = "EDUCATION_HAS_SCHOOLREF")
private Set<SchoolRef> schoolRefs = new HashSet<SchoolRef>();
public Education() {
}
public void addSchoolRef(SchoolRef schoolRef) {
getSchoolRefs().add(schoolRef);
}
Repository:
public interface UserRepository extends GraphRepository<User>, CypherDslRepository<User> {
#Query("start id=node:Identifier(identifier={0}) match id<-[:USER_HAS_IDENTIFIER]-user return user")
public User findById(String id);
Stacktrace:
Caused by: java.lang.IllegalArgumentException: Cannot obtain single field value for field 'schoolRef'
at org.springframework.data.neo4j.fieldaccess.RelatedToSingleFieldAccessorFactory$RelatedToSingleFieldAccessor.getValue(RelatedToSingleFieldAccessorFactory.java:94)
at org.springframework.data.neo4j.fieldaccess.DefaultEntityState.getValue(DefaultEntityState.java:97)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.copyEntityStatePropertyValue(SourceStateTransmitter.java:90)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.access$000(SourceStateTransmitter.java:40)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter$2.doWithAssociation(SourceStateTransmitter.java:61)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:207)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.copyPropertiesFrom(SourceStateTransmitter.java:57)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.loadEntity(Neo4jEntityConverterImpl.java:100)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.read(Neo4jEntityConverterImpl.java:92)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister$CachedConverter.read(Neo4jEntityPersister.java:170)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.createEntityFromState(Neo4jEntityPersister.java:189)
at org.springframework.data.neo4j.support.Neo4jTemplate.createEntityFromState(Neo4jTemplate.java:180)
at org.springframework.data.neo4j.fieldaccess.RelationshipHelper.createEntitySetFromRelationshipEndNodes(RelationshipHelper.java:130)
at org.springframework.data.neo4j.fieldaccess.RelatedToFieldAccessor.createEntitySetFromRelationshipEndNodes(RelatedToFieldAccessor.java:86)
at org.springframework.data.neo4j.fieldaccess.RelatedToSingleFieldAccessorFactory$RelatedToSingleFieldAccessor.getValue(RelatedToSingleFieldAccessorFactory.java:76)
at org.springframework.data.neo4j.fieldaccess.DefaultEntityState.getValue(DefaultEntityState.java:97)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.copyEntityStatePropertyValue(SourceStateTransmitter.java:90)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.access$000(SourceStateTransmitter.java:40)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter$2.doWithAssociation(SourceStateTransmitter.java:61)
at org.springframework.data.mapping.model.BasicPersistentEntity.doWithAssociations(BasicPersistentEntity.java:207)
at org.springframework.data.neo4j.support.mapping.SourceStateTransmitter.copyPropertiesFrom(SourceStateTransmitter.java:57)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.loadEntity(Neo4jEntityConverterImpl.java:100)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityConverterImpl.read(Neo4jEntityConverterImpl.java:92)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister$CachedConverter.read(Neo4jEntityPersister.java:170)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.createEntityFromState(Neo4jEntityPersister.java:189)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:244)
at org.springframework.data.neo4j.support.mapping.Neo4jEntityPersister.persist(Neo4jEntityPersister.java:231)
at org.springframework.data.neo4j.support.Neo4jTemplate.save(Neo4jTemplate.java:293)
at org.springframework.data.neo4j.support.Neo4jTemplate.save(Neo4jTemplate.java:287)
at org.springframework.data.neo4j.fieldaccess.RelationshipHelper.getOrCreateState(RelationshipHelper.java:119)
at org.springframework.data.neo4j.fieldaccess.RelationshipHelper.createSetOfTargetNodes(RelationshipHelper.java:111)
at org.springframework.data.neo4j.fieldaccess.RelatedToFieldAccessor.createSetOfTargetNodes(RelatedToFieldAccessor.java:82)
at org.springframework.data.neo4j.fieldaccess.RelatedToCollectionFieldAccessorFactory$RelatedToCollectionFieldAccessor.setValue(RelatedToCollectionFieldAccessorFactory.java:66)
at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.updateValue(ManagedFieldAccessorSet.java:94)
at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.update(ManagedFieldAccessorSet.java:82)
at org.springframework.data.neo4j.fieldaccess.ManagedFieldAccessorSet.add(ManagedFieldAccessorSet.java:108)
---- Edit:
Same error, but under different circumstances..
School school = new School();
school = neo4j.repositoryFor(School.class).save(school);
User user1 = new User("Junit", "1");
SchoolRef schoolRef1 = new SchoolRef();
schoolRef1.setSchool(school);
user1.addSchoolRef(schoolRef1);
user1 = neo4j.repositoryFor(User.class).save(user1);
User user2 = new User("Junit", "2");
SchoolRef schoolRef2 = new SchoolRef();
schoolRef2.setSchool(school);
user2.addSchoolRef(schoolRef2);
user2 = neo4j.repositoryFor(User.class).save(user2); // <- error here

sometimes I can be blind to the obvious problem...
In my case, SchoolRef references a single school, but schools can have many schoolRefs. I had incorrectly implemented School with a single reference back to SchoolRef.
I was able to create multiple SchoolRefs which referenced a single School, but got this error when I tried to fetch a School which had the multiple references.

We ran into this issue as well, but it was due to having a separate relationship with the same label.

Related

Extending SimpleNeo4jRepository in SDN 6

In SDN+OGM I used the following method to extend the base repository with additional functionality, specifically I want a way to find or create entities of different types (labels):
#NoRepositoryBean
public class MyBaseRepository<T> extends SimpleNeo4jRepository<T, String> {
private final Class<T> domainClass;
private final Session session;
public SpacBaseRepository(Class<T> domainClass, Session session) {
super(domainClass, session);
this.domainClass = domainClass;
this.session = session;
}
#Transactional
public T findOrCreateByName(String name) {
HashMap<String, String> params = new HashMap<>();
params.put("name", name);
params.put("uuid", UUID.randomUUID().toString());
// we do not use queryForObject in case of broken data with non-unique names
return this.session.query(
domainClass,
String.format("MERGE (x:%s {name:$name}) " +
"ON CREATE SET x.creationDate = timestamp(), x.uuid = $uuid " +
"RETURN x", domainClass.getSimpleName()),
params
).iterator().next();
}
}
This makes it so that I can simply add findOrCreateByName to any of my repository interfaces without the need to duplicate a query annotation.
I know that SDN 6 supports the automatic creation of a UUID very nicely through #GeneratedValue(UUIDStringGenerator.class) but I also want to add the creation date in a generic way. The method above allows to do that in OGM but in SDN the API changed and I am a bit lost.
Well, sometimes it helps to write down things. I figured out that the API did not change that much. Basically the Session is replaced with Neo4jOperations and the Class is replaced with Neo4jEntityInformation.
But even more important is that SDN 6 has #CreatedDate which makes my entire custom code redundant.

Grails unit testing working in different way

I am using grails 2.3.6 version . I have two domain class as below
class Profile {
String name
static belongsTo = Student
}
class Student {
Profile profile
Integer enrolmentNumber
}
I am trying to create one to one unidirectional relationship . The problem is I have written a unit test case
#TestFor(Student)
#Mock([Profile,Student])
class StudentSpec extends Specification {
void "test save Student"() {
when :"Student details"
Profile profile = new Profile(name:"Test")
Student student = new Student(enrolmentNumber: 10,profile:profile)
then : " Student should be saved"
assertNotNull(student.save(flush: true))
assertNotNull(profile.id)
when:"When I delete the student"
student.delete()
then:"Profile should also deleted"
assertNull Profile.findById(profile.id)
}
}
test is failing at the last line "Profile.findById(profile.id)" saying profile is found. As per my understanding when I have "belongsTo" then cascading should happen ,I mean when I delete the Student, Profile should also be delete . But its not happening .
Instead of creating a unit test , I used the same code in Integration test , its working correctly.
Not Sure what I am doing wrong. Or my understanding is wrong.
Try
student.delete(flush:true)

EventReceiver not Firing on SharePoint List

I am trying to create an EventReceiver for a blog site (for the Posts list) and am having some trouble getting it working. I want to change the Created By column to Anonymous. Basically I have this whole thing working in a console application, however, that will only change the Created By column names when the console application is executed.
I need it to change the Created By whenever a new item is added to the list. My code is below....how do I modify this to use in an EventReceiver project??? Since I already tell the EventReceiver project the URL I want the EventReceiver attached to, I'm not sure what I can remove from this code, right now it just doesn't do anything, no error and no changing of the Created By column when I debug.
using (SPSite site = new SPSite("http://test-sharepoint/subsite/"))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists["Posts"];
SPListItemCollection listItemCollection = list.Items;
foreach (SPListItem listItem in listItemCollection)
{
SPFieldUserValue userName = new SPFieldUserValue(web, 22, "Anonymous");
listItem["Author"] = userName;
listItem["Editor"] = userName;
listItem.Update();
}
web.Update();
}
}
EDIT: Code is in ItemAdded method
EDIT #2: This is trying the same code except without the loop and using properties.ListItem, this was my attempt in a Event Recevier project but no luck. It just doesn't change the Created By field, or any field for that matter (I tried the Title as well)
SPSite site = new SPSite("http://test-sharepoint/subsite/");
SPWeb web = site.OpenWeb();
SPFieldUserValue userName = new SPFieldUserValue(web, 22, "Anonymous");
properties.ListItem["Author"] = userName;
properties.ListItem["Editor"] = userName;
properties.ListItem.Update();
*Also from my understanding the SPFieldUserValue will grab either a User or a SharePoint User Group (Permissions) so in my code, the 22 grabs the SharePoint User Group that I want and "Anonymous" is the user from that group...
EDIT #3: More progress, this code works without issues for a list, however, not for the Posts or Comments lists, for those it does not change the Created By field. Could it be because of the approve/reject for all items??? Whether approved orpending it still does not show annonymous, BUT like I mentioned, it works fine in a different list.
public override void ItemAdded(SPItemEventProperties properties)
{
base.ItemAdded(properties);
SPSite site = new SPSite("http://test-sharepoint/hr/blog/"); //SPContext.Current.Site;
SPWeb web = site.OpenWeb();
SPFieldUserValue userName = new SPFieldUserValue(web,22,"Anonymous");
SPListItem currentItem = properties.ListItem;
//currentItem["Title"] = userName; //DateTime.Now.ToString();
currentItem["Author"] = userName;
currentItem["Editor"] = userName;
currentItem.SystemUpdate();
}
**EDIT #4: Alright I found my issue, when creating the project I chose Custom List as my list to attach to but I needed to choose Posts or Comments and now the above code works!!!
But now I have another problem, all posts on the blog are first submitted for approval...and due to this the event receiver doesn't seem to work for users other than the admin. It works fine for the admin account where I can just directly publish a post or comment but for a user with Contribute permissions whose posts are submitted for approval still shows their name on the Manage Posts page...what could I do about this? Any ideas?**
The code that works:
public override void ItemAdded(SPItemEventProperties properties)
{
base.ItemAdded(properties);
SPSite site = new SPSite("http://test-sharepoint/hr/blog/"); //SPContext.Current.Site;
SPWeb web = site.OpenWeb();
SPFieldUserValue userName = new SPFieldUserValue(web, 23, "Anonymous");
SPListItem currentItem = properties.ListItem;
currentItem["Author"] = userName;
currentItem["Editor"] = userName;
currentItem.SystemUpdate();
}
In response to edit #4, when working with SharePoint, if code works when executed by the administrator account, but does not work when executed by a "normal" account, permissions are likely to blame.
See the answer to the question SharePoint/WSS: Modify “created by” field? for an example of an SPItemEventReceiver that modifies the Author field.
Note: Many SharePoint developers recommend against the use of RunWithElevatedPrivileges and suggest using impersonation instead. See my answer to the question In which situation use SPSecurity.RunWithElevatedPrivileges with superusertoken? for more details.

JPA - How to avoid getting an empty list?

I'm creating a sort of a social networking site, like Facebook, as a university project. Users can upload photos, but I'm somehow unable to retrieve the list of photos for a particular user.
Here's how I'm doing it right now:
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
private String emailAddress;
private String password;
private String firstName;
private String lastName;
(...)
#OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
private List<Photo> photos;
public User() {
}
(...)
public void addPhoto( Photo photo){
photos.add(photo);
}
public List<Photo> getPhotos() {
return photos;
}
}
And here's the Photo entity:
#Entity
public class Photo implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String url;
private String label;
#ManyToOne
private User owner;
public Photo() {
}
(...)
public User getOwner() {
return owner;
}
}
Each photo is uploaded by creating a post that contains it. Here's the EJB that does it:
#Stateless
public class PublicPost implements PublicPostRemote {
#PersistenceContext
EntityManager em;
#Override
public void createPost(LoginUserRemote loginUserBean, String targetEmail, final String content, final String photoURL) {
if (loginUserBean.isLoggedIn()) {
final User author = loginUserBean.getLoggedUser();
System.out.println(targetEmail);
final User target = em.find(User.class, targetEmail);
if (author != null && target != null) {
//See if there's a photo to post as well
Photo photo = null;
if (photoURL != null) {
photo = new Photo(photoURL, author, content);
em.persist(photo);
}
MessageBoard publicMessageBoard = target.getPublicMessageBoard();
Post post = new Post(author, content);
post.setMessageBoard(publicMessageBoard);
if (photo != null) {
post.setPostPhoto(photo);
}
em.persist(post);
em.refresh(publicMessageBoard);
//Send an e-mail to the target (if the author and the target are different)
if (!author.getEmailAddress().equals(target.getEmailAddress())) {
final String subject = "[PhaseBook] " + author.getEmailAddress() + " has posted on your public message board.";
Thread mailThread = new Thread() {
#Override
public void run() {
try {
GMailSender.sendMessage(target.getEmailAddress(), subject, content);
} catch (MessagingException ex) {
Logger.getLogger(PublicPost.class.getName()).log(Level.SEVERE, null, ex);
}
}
};
mailThread.start();
}
}
}
}
}
So what happens is: I create a new post that contains a photo, yet later, when I use this, on the web tier...
LoginUserRemote lur = (LoginUserRemote)session.getAttribute("loginUserBean");
User user = lur.getLoggedUser();
List<Photo> photos = user.getPhotos();
System.out.println();
System.out.println("This user has this many photos: " + photos.size());
...it always tells me that the user has 0 photos. Why is this? Am I defining the relationship between user and photo incorrectly? Am I forgetting to persist/refresh anything? Or does the problem lie somewhere else?
If you store a detached User object (the logged in user) in the HTTP session, and then create and persists photos having this detached user as owner, JPA won't automatically add the photo to the detached user. For the entity manager, this detached user doesn't exist: it's not under its responsibility anymore.
Even if User was still attached, it's your responsibility to maintain the coherence of the object graph. If you modify one side of the association (by setting the user as owner of the photo), you should also modify the other side (by adding the photo to the list of photos of the owner).
I'm not absolutely sure this is the cause of the problem, because you haven't shown us what the loginUserBean was and did to get the logged in user, but it might be the answer.
There is a series of issues here:
Are photos actually stored in the database? Maybe you don't have a transaction open?
You are not updating both sides of the association.
Theoretically you only need to update the owning side, but better be safe than sorry:
photo = new Photo(photoURL, author, content);
em.persist(photo);
author.addPhoto(photo);
You are fetching the user from a session and then retrieving associated collection of photos. Do you really know what this means? If the user has hundreds of photos, do you really want to store them in HTTP session along with the user all the time? This is not how Facebook works ;-).
I think refreshing your entity (with em.refresh(lur.getLoggedUser())) might work, but only at university, not in real life. Loading all the user photos at once into memory is an overkill. Personally I would even remove photos association from user to avoid this. Load one page at a time and only on demand.
Even if you know what you are doing or such a behaviour is acceptable, objects stored in HTTP session are so called detached from persistence context, meaning your persistence provider does no longer keep track of them. So adding a photo does not mean that the photos collection will be magically updated in every object. I think about carefully, this would be even worse.
Last but not least, your createPost() really needs some code review. It does at least 4 things at once, System.out, one time threads created on demand, silently doing nothing when preconditions are not met (like user not being logged in, missing parameters), mixing concerns on different level of abstraction. Don't want to be too meticulous, but your grade might be influenced by the quality of code.

Can't access a new field programmatically on a template in Sitecore

my question is basically the same as #Bob Black's Cannot access sitecore item field via API but I agree with #techphoria414 that the accepted solution is not necessary and in my case does not work.
In my own words, I have a template Departure that I have been using for about a year now creating and updating items programmatically. I have added a new field Ship to the template. When I create a new item the field comes up as null when I try to access it using departure.Fields["Ship"]. If I step over the line causing the exception then after calling departure.Editing.EndEdit() I can then see the Ship field if I call departure.Fields.ToList(). If I add the template to a content item via the Sitecore GUI I can see the field and use it, and if I look at a content item which is based on the template I can see the new field too. So it is only when I access the template/item programmatically that it is null.
I have sitecore running on my local machine with a local sqlserver, and publish to my local machine.
Here is my code
String ship = "MSDisaster";
foreach (Language language in SiteLanguages)
{
departure = departure.Database.GetItem(departure.ID, language);
departure.Editing.BeginEdit();
try
{
departure.Fields["StartDate"].Value = GetSitecoreDateString(xDep, "StartDate");
departure.Fields["EndDate"].Value = GetSitecoreDateString(xDep, "EndDate");
departure.Fields["Guaranteed"].Value = xDep.SelectSingleNode("./Guaranteed").InnerText;
departure.Fields["Status"].Value = xDep.SelectSingleNode("./Status").InnerText;
departure.Fields["Currency"].Value = ConvertLanguageToCurrency(language);
departure.Fields["Market"].Value = ConvertLanguageToMarket(language);
departure.Fields["TwinSharePrice"].Value = GetPrice(xDep, "twn", language);
departure.Fields["SinglePrice"].Value = GetPrice(xDep, "sgl", language);
if (!String.IsNullOrEmpty(ship))
departures.Fields["Ship"].Value = ship;
}
catch (Exception ex)
{
departure.Editing.CancelEdit();
log.Error(ex);
throw ex;
}
departure.Editing.EndEdit();
}
So, how do I get the field be picked up?
Thanks,
James.
Firstly do you see the field in the web database in the sitecore administration.
If you do the item has the fields, you then should check the template assigned on the item and double check that the field is actually called "ship" and check the case as ive seen this as an issue before.
Also check the security on the item and field just in case anyone changed anything.
Next try and get the data from the item but instead of using the field name, use the field ID.
Let me know how you go?
Chris
Sorry Chris, StackOverflow, and the others who looked at my questions. It was a stupid typo. It's even there in my question
departure.Fields["SinglePrice"].Value = GetPrice(xDep, "sgl", language);
if (!String.IsNullOrEmpty(ship))
departures.Fields["Ship"].Value = ship;
}
departure is the item I am working on, departures is the collection it belongs to... doh.
So what is the protocol here? Do I delete my question now because it isn't really going to help anyone code better?
James.