DATAGRAPH-876 - Derived finder filter values are not thread safe.

This commit is contained in:
Luanne Misquitta
2016-06-09 10:20:20 +05:30
parent f097bd5cae
commit 36924f0d59
5 changed files with 161 additions and 5 deletions

View File

@@ -0,0 +1,79 @@
/*
* Copyright (c) [2011-2016] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product may include a number of subcomponents with
* separate copyright notices and license terms. Your use of the source
* code for these subcomponents is subject to the terms and
* conditions of the subcomponent's license, as noted in the LICENSE file.
*
*/
package org.springframework.data.neo4j.repository.query.derived;
import org.neo4j.ogm.cypher.BooleanOperator;
import org.neo4j.ogm.cypher.ComparisonOperator;
import org.neo4j.ogm.cypher.Filter;
/**
* A representation of a Neo4j-OGM Filter that contains no parameter/property values and only holds metadata
* @author Luanne Misquitta
*/
public class CypherFilter {
Integer propertyPosition;
String propertyName;
Class ownerEntityType;
ComparisonOperator comparisonOperator;
boolean negated;
BooleanOperator booleanOperator;
Class nestedPropertyType;
String nestedPropertyName;
public void setPropertyPosition(Integer propertyPosition) {
this.propertyPosition = propertyPosition;
}
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
public void setOwnerEntityType(Class ownerEntityType) {
this.ownerEntityType = ownerEntityType;
}
public void setComparisonOperator(ComparisonOperator comparisonOperator) {
this.comparisonOperator = comparisonOperator;
}
public void setNegated(boolean negated) {
this.negated = negated;
}
public void setBooleanOperator(BooleanOperator booleanOperator) {
this.booleanOperator = booleanOperator;
}
public void setNestedPropertyType(Class nestedPropertyType) {
this.nestedPropertyType = nestedPropertyType;
}
public void setNestedPropertyName(String nestedPropertyName) {
this.nestedPropertyName = nestedPropertyName;
}
Filter toFilter() {
Filter filter = new Filter();
filter.setPropertyPosition(propertyPosition);
filter.setPropertyName(propertyName);
filter.setOwnerEntityType(ownerEntityType);
filter.setComparisonOperator(comparisonOperator);
filter.setNegated(negated);
filter.setBooleanOperator(booleanOperator);
filter.setNestedPropertyType(nestedPropertyType);
filter.setNestedPropertyName(nestedPropertyName);
return filter;
}
}

View File

@@ -12,9 +12,11 @@
*/
package org.springframework.data.neo4j.repository.query.derived;
import java.util.ArrayList;
import java.util.List;
import org.neo4j.ogm.cypher.BooleanOperator;
import org.neo4j.ogm.cypher.ComparisonOperator;
import org.neo4j.ogm.cypher.Filter;
import org.neo4j.ogm.cypher.Filters;
import org.springframework.data.repository.query.parser.Part;
@@ -27,7 +29,7 @@ public class CypherFinderQuery implements DerivedQueryDefinition {
private Class<?> entityType;
private Part basePart;
private Filters parameters = new Filters();
private List<CypherFilter> cypherFilters = new ArrayList<>();
private int paramPosition = 0;
public CypherFinderQuery(Class<?> entityType, Part basePart) {
@@ -42,13 +44,17 @@ public class CypherFinderQuery implements DerivedQueryDefinition {
@Override
public Filters getFilters() {
return parameters;
Filters filters = new Filters();
for (CypherFilter cypherFilter : cypherFilters) {
filters.add(cypherFilter.toFilter());
}
return filters;
}
@Override
public void addPart(Part part, BooleanOperator booleanOperator) {
String property = part.getProperty().getSegment();
Filter parameter = new Filter();
CypherFilter parameter = new CypherFilter();
parameter.setPropertyPosition(paramPosition++);
parameter.setPropertyName(property);
parameter.setOwnerEntityType(entityType);
@@ -62,7 +68,7 @@ public class CypherFinderQuery implements DerivedQueryDefinition {
parameter.setPropertyName(part.getProperty().getLeafProperty().getSegment());
parameter.setNestedPropertyName(part.getProperty().getSegment());
}
parameters.add(parameter);
cypherFilters.add(parameter);
}

View File

@@ -20,6 +20,7 @@ public class Person {
private Long id;
private String name;
private String surname;
public String getName() {
@@ -30,6 +31,14 @@ public class Person {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public Long getId() {
return id;
}

View File

@@ -110,4 +110,6 @@ public interface UserRepository extends PersonRepository<User> {
@Query("match (u:User)-[r:RATED]->(m:Movie) return u as user, collect({username: u.name, movietitle: m.title, stars:r.stars}) as literalMap")
List<EntityWrappingQueryResult> findRatingsWithLiteralMap();
User findBySurname(String surname);
}

View File

@@ -39,6 +39,9 @@ import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static org.junit.Assert.*;
@@ -469,4 +472,61 @@ public class DerivedQueryTest extends MultiDriverTestClass {
assertEquals(2, row.getLiteralMap().size());
}
}
/**
* @see DATAGRAPH-876
* @throws InterruptedException
*/
@Test
public void shouldAllowMultiThreadedDerivedFinderExecution() throws InterruptedException {
int numThreads = 3;
executeUpdate("CREATE (m:User {name:'Michal', surname:'Bachman'}), (a:User {name:'Adam', surname:'George'}), (l:User {name:'Luanne', surname:'Misquitta'})");
session.clear();
String[] firstNames = new String[] {"Michal", "Adam", "Luanne"};
String[] lastNames = new String[] {"Bachman", "George", "Misquitta"};
ExecutorService executor = Executors.newFixedThreadPool(numThreads);
CountDownLatch latch = new CountDownLatch(numThreads);
for (int i = 0; i < numThreads; i++) {
executor.submit(new DerivedQueryRunner(latch, firstNames[i], lastNames[i]));
}
latch.await(); // pause until the count reaches 0
// force termination of all threads
executor.shutdownNow();
}
class DerivedQueryRunner implements Runnable {
private final CountDownLatch latch;
private final String firstName;
private final String lastName;
public DerivedQueryRunner(CountDownLatch latch, String firstName, String lastName ) {
this.latch = latch;
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public void run() {
try
{
User user = userRepository.findBySurname(lastName);
assertNotNull(user);
assertEquals(firstName, user.getName());
assertEquals(lastName, user.getSurname());
} finally {
latch.countDown();
}
}
}
}