I am attempting to create a server plugin in neo4j to make a specific query and wish to return, not one iterable, but two iterables of Node.
I saw that this is not possible according to the neo4j docs, so I tried to create an array of JSONObject from these arrays and then return it as server plugin result. But it seems that this does not work.
So I am asking if someone has already done such thing?
I have been told on neo4j google group to use Gremlin, but have never use it before and think it is a bit complicated.
Any help would be very appreciated.
Thanks
i eventually got around the problem by merging the two lists i wanted to return before returning a unique list. Hence i could separate them in my python code, since i know where starts each one.
public class Ond extends ServerPlugin {
#PluginTarget(GraphDatabaseService.class)
public static Iterable<Node> getOnd(
#Source GraphDatabaseService graphDb,
#Description("the airline's node ID") #Parameter(name = "id") int id) {
List<Node> results= new ArrayList<Node>();
String n4jQuery= "START al= node("+id+") match ond-[:operatedBy]->al, ond-[:origin]->orig, ond-[:destination]->dest RETURN orig, dest ;";
ExecutionEngine engine= new ExecutionEngine(graphDb);
ExecutionResult result= engine.execute(n4jQuery);
List<Node> orig= new ArrayList<Node>();
List<Node> dest= new ArrayList<Node>();
//creating the lists i want to return
//an outter loop over tables rows
for (Map<String, Object> row : result) {
//an inner loop over the two columns : orig and dest
for (Map.Entry<String, Object> column : row.entrySet()) {
String key = column.getKey();
Node n = (Node) column.getValue();
if(key.equals("dest")){
dest.add(n);
}else{
orig.add(n);
}
}
}
//merging the two lists
results.addAll(orig);
results.addAll(dest);
// orig elements are between indices 0 and size(results)/2 -1
//and dest element are between size(results)/2 and size(results)-1
return results;
}
}
Hope it helps !!
Related
I am trying to write a custom converter for a nested object so that this object gets saved as string in Neo4j database.
I am using #Convert annotation on my field and passing ImageConverter.class which is my AttributeConverter class.
Everything works fine as expected and I am able to save string representation of Image class in Neo4j db.
However, now instead of single image I want to have List<Image> as my nested field. In this case, putting #Convert(ImageConverter.class) doesn't work.
I see that there is a class called ConverterBasedCollectionConverter which gets used when I have a field of type List<LocalDateTime.
However, I couldn't find any exammples on how to use this class in case of custom converters.
Please can anyone help me with this or if there is any other approach to use custom converter on field of type List.
I am using Neo4j (version 3.4.1) and Spring-data-neo4j (5.0.10.RELEASE) in my application. I am also using OGM.
PS: I am aware that it is advised to store nested objects as separate node establishing a relationship with parent object. However, my use case demands that the object be stored as string property and not as separate node.
Regards,
V
It is not so difficult as I assumed it would be.
Given a class (snippet)
#NodeEntity
public class Actor {
#Id #GeneratedValue
private Long id;
#Convert(MyImageListConverter.class)
public List<MyImage> images = new ArrayList<>();
// ....
}
with MyImage as simple as can be
public class MyImage {
public String blob;
public MyImage(String blob) {
this.blob = blob;
}
public static MyImage of(String value) {
return new MyImage(value);
}
}
and a converter
public class MyImageListConverter implements AttributeConverter<List<MyImage>, String[]> {
#Override
public String[] toGraphProperty(List<MyImage> value) {
if (value == null) {
return null;
}
String[] values = new String[(value.size())];
int i = 0;
for (MyImage image : value) {
values[i++] = image.blob;
}
return values;
}
#Override
public List<MyImage> toEntityAttribute(String[] values) {
List<MyImage> images = new ArrayList<>(values.length);
for (String value : values) {
images.add(MyImage.of(value));
}
return images;
}
}
will print following debug output on save that I think is what you want:
UNWIND {rows} as row CREATE (n:Actor) SET n=row.props RETURN row.nodeRef as ref, ID(n) as id, {type} as type with params {type=node, rows=[{nodeRef=-1, props={images=[blobb], name=Jeff}}]}
especially the images part.
Test method for this looks like
#Test
public void test() {
Actor jeff = new Actor("Jeff");
String blobValue = "blobb";
jeff.images.add(new MyImage(blobValue));
session.save(jeff);
session.clear();
Actor loadedActor = session.load(Actor.class, jeff.getId());
assertThat(loadedActor.images.get(0).blob).isEqualTo(blobValue);
}
I am came up with a solution to my problem. So, in case you want another solution along with the solution provided by #meistermeier, you can use the below code.
public class ListImageConverter extends ConverterBasedCollectionConverter<Image, String>{
public ListImageConverter() {
super(List.class, new ImageConverter());
}
#Override
public String[] toGraphProperty(Collection<Image> values) {
Object[] graphProperties = super.toGraphProperty(values);
String[] stringArray = Arrays.stream(graphProperties).toArray(String[]::new);
return stringArray;
}
#Override
public Collection<Image> toEntityAttribute(String[] values) {
return super.toEntityAttribute(values);
}
}
ImageConverter class just implements AttributeConverter<Image, String> where I serialize and deserialize my Image object to/from json.
I chose to go with this approach because I had Image field in one object and List<Image> in another object. So just by changing #Convert(ListImageConverter.class) to #Convert(ImageConverter.class) I was able to save list as well as single object in Neo4j database.
Note: You can skip overriding toEntityAttribute method if you want. It doesn't add much value.
However you have to override toGraphProperty as within Neo4j code it checks for presence of declared method with name toGraphProperty.
Hope this helps someone!
Regards,
V
We have a problem making asList() method sortable.
We thought we could do this by just extending the View class and override the asList method but realized that View class has a private constructor so we could not do this.
Our other attempt was to fork the Google Dataflow code on github and modify the PCollectionViews class to return a sorted list be using the Collections.sort method as shown in the code snippet below
#Override
protected List<T> fromElements(Iterable<WindowedValue<T>> contents) {
Iterable<T> itr = Iterables.transform(
contents,
new Function<WindowedValue<T>, T>() {
#SuppressWarnings("unchecked")
#Override
public T apply(WindowedValue<T> input){
return input.getValue();
}
});
LOG.info("#### About to start sorting the list !");
List<T> tempList = new ArrayList<T>();
for (T element : itr) {
tempList.add(element);
};
Collections.sort((List<? extends Comparable>) tempList);
LOG.info("##### List should now be sorted !");
return ImmutableList.copyOf(tempList);
}
Note that we are now sorting the list.
This seemed to work, when run with the DirectPipelineRunner but when we tried the BlockingDataflowPipelineRunner, it didn't seem like the code change was being executed.
Note: We actually recompiled the dataflow used it in our project but this did not work.
How can we be able to achieve this (as sorted list from the asList method call)?
The classes in PCollectionViews are not intended for extension. Only the primitive view types provided by View.asSingleton, View.asSingleton View.asIterable, View.asMap, and View.asMultimap are supported.
To obtain a sorted list from a PCollectionView, you'll need to sort it after you have read it. The following code demonstrates the pattern.
// Assume you have some PCollection
PCollection<MyComparable> myPC = ...;
// Prepare it for side input as a list
final PCollectionView<List<MyComparable> myView = myPC.apply(View.asList());
// Side input the list and sort it
someOtherValue.apply(
ParDo.withSideInputs(myView).of(
new DoFn<A, B>() {
#Override
public void processElement(ProcessContext ctx) {
List<MyComparable> tempList =
Lists.newArrayList(ctx.sideInput(myView));
Collections.sort(tempList);
// do whatever you want with sorted list
}
}));
Of course, you may not want to sort it repeatedly, depending on the cost of sorting vs the cost of materializing it as a new PCollection, so you can output this value and read it as a new side input without difficulty:
// Side input the list, sort it, and put it in a PCollection
PCollection<List<MyComparable>> sortedSingleton = Create.<Void>of(null).apply(
ParDo.withSideInputs(myView).of(
new DoFn<Void, B>() {
#Override
public void processElement(ProcessContext ctx) {
List<MyComparable> tempList =
Lists.newArrayList(ctx.sideInput(myView));
Collections.sort(tempList);
ctx.output(tempList);
}
}));
// Prepare it for side input as a list
final PCollectionView<List<MyComparable>> sortedView =
sortedSingleton.apply(View.asSingleton());
someOtherValue.apply(
ParDo.withSideInputs(sortedView).of(
new DoFn<A, B>() {
#Override
public void processElement(ProcessContext ctx) {
... ctx.sideInput(sortedView) ...
// do whatever you want with sorted list
}
}));
You may also be interested in the unsupported sorter contrib module for doing larger sorts using both memory and local disk.
We tried to do it the way Ken Knowles suggested. There's a problem for large datasets. If the tempList is large (so sort takes some measurable time as it's proportion to O(n * log n)) and if there are millions of elements in the "someOtherValue" PCollection, then we are unecessarily re-sorting the same list millions of times. We should be able to sort ONCE and FIRST, before passing the list to the someOtherValue.apply's DoFn.
I am new to Neo4j and I develop the project using c#(Neo4jClient).
In my project i want to create approximately 3000 nodes at a time. Now I create single node by node because to avoid duplication's(i.e i check each time nodes exists or not. if only not exists then i create nodes.). now in neo4j have 1,60,000 nodes. so it will take 2 hours to complete 3000 nodes.
I would like to use Batch Insertion. Please share me code to use batch insertion at this same to check duplication node. Thanks in advance.
Example
public class Neo4jDataProvider<T>
{
IGraphClient _client = null;
public Neo4jDataProvider(IGraphClient client)
{
_client = client;
}
public void CreateAll(IEnumerable<T> records)
{
if (_client != null)
{
var propKey = string.Format("{0}s", typeof (T).Name.ToLower());
var query = _client.Cypher;
var createString = string.Format("({0}:{1} {{{2}}})", "record", typeof(T).Name, propKey);
query = query.Create(createString);
query = query.WithParam(propKey, records.ToList());
query.ExecuteWithoutResults();
}
}
}
I have a simple relationship test that I am trying to run to create a unique node using Rest API (java-rest-binding) https://github.com/neo4j/java-rest-binding but unfortunately I am stuck on something, here are the details: (the non-unique node and relationship works perfectly fine, its with this that it doesn't, most likely I am doing something naive (pardon my lack on knowledge of neo4j).
final UserModel userModel = new UserModel();
final HashMap<String, Object> uModelAttributes = new HashMap<String, Object>(0);
uModelAttributes.put("name", "AnirudhVyas");
userModel.setAttributes(uModelAttributes);
final HashSet<Action> buyHistory = new HashSet<Action>();
final Action buyAction = new Action();
final ProductModel productModel = new ProductModel();
final HashMap<String, Object> attributes = new HashMap<String, Object>(0);
attributes.put("name", "mercedes benz ");
attributes.put("make", "mercedes benz");
attributes.put("model", "sls 550");
attributes.put("year", "2014");
productModel.setAttributes(attributes);
buyAction.setProduct(productModel);
buyHistory.add(buyAction);
userModel.setBuyHistory(buyHistory);
System.out.println("Before");
new UserModelDAO().createCompleteTree(userModel);
System.out.println("Completed >>>
if i use this on the dao:
final RestNode root = api.getOrCreateNode(api.index().forNodes("users", MapUtil.stringMap(IndexManager.PROVIDER, "lucene", "type", "fulltext")), "name", m
.getAttributes().get("name"), m.getAttributes());
api.getOrCreateNode(api.index().forNodes("products", MapUtil.stringMap(IndexManager.PROVIDER, "lucene", "type", "fulltext")), "name", buyAction.getProduct().getAttributes().get("name"), buyAction.getProduct().getAttributes()), RelationshipTypes.BOUGHT);
This basically Fails with:
java.lang.RuntimeException: Error retrieving or creating node for key name and value AnirudhVyas with index users
at org.neo4j.rest.graphdb.ExecutingRestAPI.getOrCreateNode(ExecutingRestAPI.java:448)
at org.neo4j.rest.graphdb.RestAPIFacade.getOrCreateNode(RestAPIFacade.java:223)
at xxxx.xxxx.xxxx.graph.UserModelCreateTasteKeyNeo4JBatchCallback.recordBatch(UserModelCreateTasteKeyNeo4JBatchCallback.java:61)
There are several ways to do it, one of them is using Cypher:
MATCH a
WHERE a.name! = 'nameTofound'
CREATE UNIQUE a-[:Relationship]-c:LABEL
RETURN c
Use it inside a queryEngine.
It works like a charm, please look at the following link for more details:
http://docs.neo4j.org/chunked/milestone/query-create-unique.html
Java code is here:
protected static RestNode createOrGetExistingNode(String key, String valueFor, String Rel, String label){
RestNode node = null;
final QueryResult<Map<String, Object>> result = GraphRestService.queryEngine.query(String.format("MATCH node WHERE node.%s! ='%s' " +
"CREATE UNIQUE node-[:%s]-c:%s RETURN c" , key, valueFor, Rel, label), MapUtil.map("reference", 0));
for (Map<String, Object> column : result) {
node = (RestNode) column.get("c");
}
return node;
}
I solved the problem by not using batch rest callback - When I simply use - getOrCreateXXX( ) for RestAPI it works like a charm - Need to investigate further as to why getOrCreate on BatchCallback#recordBatch( ) will behave differently.
I am looking for best solutions for the following question.
We are given a binary tree, we need to generate a linked list from the tree using pre order traversal. Also write a test case to check whether its correct.
If you can provide a solution specific to a generating linked list and testing. Also what would be best complexity that can be achieved for this solution.
I have tried something like this :
public void TraversePreOrder(TreeNode node, LinkedListNode head){
if(node != null) return;
//Insert current node into linkedList
head = insertNodeIntoLinkedList(head, node.data);
PreOrder(node.left, head);
PreOrder(node.right, head);
}
public LinkedListNode insertNodeIntoLinkedList(LinkedListNode head, int data){
LinkedListNode newNode = new LinkedListNode(data);
if(head == null) {
head = newNode;
return head;
}
Node currentNode = head;
while(currentNode.next != null){
currentNode = currentNode.next;
}
currentNode.next = newNode;
return head;
}
Usually in any traversal order we print the data from the node that we come across on the screen. However, you can easily replace the print function with an insert function.
Create a linked list and a function to insert data in your linked list. Then create your tree, input data from it from the user (if that is what you want) then create a function for preorder traversel and instead of printing the date on the screen you can add it to your linked list.