Created new Neo4J ItemReader to support Spring Data 4

This adds a new ItemReader that works with the Spring Data 4 updated
Neo4JOperations implementation as well as the updated data conversion
techniques.

Resolves BATCH-2473
This commit is contained in:
Michael Minella
2016-04-14 15:11:13 -05:00
parent 164bf175c9
commit b160996576
11 changed files with 953 additions and 174 deletions

315
.gradletasknamecache Normal file
View File

@@ -0,0 +1,315 @@
All
assemble
spring-batch-core:assemble
spring-batch-core-tests:assemble
spring-batch-infrastructure:assemble
spring-batch-infrastructure-neo4j4:assemble
spring-batch-infrastructure-tests:assemble
spring-batch-integration:assemble
spring-batch-samples:assemble
spring-batch-test:assemble
spring-build-src:assemble
build
spring-batch-core:build
spring-batch-core-tests:build
spring-batch-infrastructure:build
spring-batch-infrastructure-neo4j4:build
spring-batch-infrastructure-tests:build
spring-batch-integration:build
spring-batch-samples:build
spring-batch-test:build
spring-build-src:build
spring-batch-core:buildDependents
spring-batch-core-tests:buildDependents
spring-batch-infrastructure:buildDependents
spring-batch-infrastructure-neo4j4:buildDependents
spring-batch-infrastructure-tests:buildDependents
spring-batch-integration:buildDependents
spring-batch-samples:buildDependents
spring-batch-test:buildDependents
spring-build-src:buildDependents
spring-batch-core:buildNeeded
spring-batch-core-tests:buildNeeded
spring-batch-infrastructure:buildNeeded
spring-batch-infrastructure-neo4j4:buildNeeded
spring-batch-infrastructure-tests:buildNeeded
spring-batch-integration:buildNeeded
spring-batch-samples:buildNeeded
spring-batch-test:buildNeeded
spring-build-src:buildNeeded
spring-batch-core:classes
spring-batch-core:compileJava
spring-batch-core:processResources
spring-batch-core-tests:classes
spring-batch-core-tests:compileJava
spring-batch-core-tests:processResources
spring-batch-infrastructure:classes
spring-batch-infrastructure:compileJava
spring-batch-infrastructure:processResources
spring-batch-infrastructure-neo4j4:classes
spring-batch-infrastructure-neo4j4:compileJava
spring-batch-infrastructure-neo4j4:processResources
spring-batch-infrastructure-tests:classes
spring-batch-infrastructure-tests:compileJava
spring-batch-infrastructure-tests:processResources
spring-batch-integration:classes
spring-batch-integration:compileJava
spring-batch-integration:processResources
spring-batch-samples:classes
spring-batch-samples:compileJava
spring-batch-samples:processResources
spring-batch-test:classes
spring-batch-test:compileJava
spring-batch-test:processResources
spring-build-src:classes
spring-build-src:compileGroovy
spring-build-src:compileJava
spring-build-src:processResources
spring-batch-core:generateSql
spring-batch-core:jar
spring-batch-core-tests:jar
spring-batch-infrastructure:jar
spring-batch-infrastructure-neo4j4:jar
spring-batch-infrastructure-tests:jar
spring-batch-integration:jar
spring-batch-samples:jar
spring-batch-test:jar
spring-build-src:jar
spring-batch-core:testClasses
spring-batch-core:compileTestJava
spring-batch-core:processTestResources
spring-batch-core-tests:testClasses
spring-batch-core-tests:compileTestJava
spring-batch-core-tests:processTestResources
spring-batch-infrastructure:testClasses
spring-batch-infrastructure:compileTestJava
spring-batch-infrastructure:processTestResources
spring-batch-infrastructure-neo4j4:testClasses
spring-batch-infrastructure-neo4j4:compileTestJava
spring-batch-infrastructure-neo4j4:processTestResources
spring-batch-infrastructure-tests:testClasses
spring-batch-infrastructure-tests:compileTestJava
spring-batch-infrastructure-tests:processTestResources
spring-batch-integration:testClasses
spring-batch-integration:compileTestJava
spring-batch-integration:processTestResources
spring-batch-samples:testClasses
spring-batch-samples:compileTestJava
spring-batch-samples:processTestResources
spring-batch-test:testClasses
spring-batch-test:compileTestJava
spring-batch-test:processTestResources
spring-build-src:testClasses
spring-build-src:compileTestGroovy
spring-build-src:compileTestJava
spring-build-src:processTestResources
init
depsZip
dist
distZip
docsZip
schemaZip
api
spring-build-src:groovydoc
spring-batch-core:javadoc
spring-batch-core-tests:javadoc
spring-batch-infrastructure:javadoc
spring-batch-infrastructure-neo4j4:javadoc
spring-batch-infrastructure-tests:javadoc
spring-batch-integration:javadoc
spring-batch-samples:javadoc
spring-batch-test:javadoc
spring-build-src:javadoc
reference
referenceEpub
referenceHtmlMulti
referenceHtmlSingle
referencePdf
components
spring-batch-core:components
spring-batch-core-tests:components
spring-batch-infrastructure:components
spring-batch-infrastructure-neo4j4:components
spring-batch-infrastructure-tests:components
spring-batch-integration:components
spring-batch-samples:components
spring-batch-test:components
spring-build-src:components
dependencies
spring-batch-core:dependencies
spring-batch-core-tests:dependencies
spring-batch-infrastructure:dependencies
spring-batch-infrastructure-neo4j4:dependencies
spring-batch-infrastructure-tests:dependencies
spring-batch-integration:dependencies
spring-batch-samples:dependencies
spring-batch-test:dependencies
spring-build-src:dependencies
dependencyInsight
spring-batch-core:dependencyInsight
spring-batch-core-tests:dependencyInsight
spring-batch-infrastructure:dependencyInsight
spring-batch-infrastructure-neo4j4:dependencyInsight
spring-batch-infrastructure-tests:dependencyInsight
spring-batch-integration:dependencyInsight
spring-batch-samples:dependencyInsight
spring-batch-test:dependencyInsight
spring-build-src:dependencyInsight
help
spring-batch-core:help
spring-batch-core-tests:help
spring-batch-infrastructure:help
spring-batch-infrastructure-neo4j4:help
spring-batch-infrastructure-tests:help
spring-batch-integration:help
spring-batch-samples:help
spring-batch-test:help
spring-build-src:help
model
spring-batch-core:model
spring-batch-core-tests:model
spring-batch-infrastructure:model
spring-batch-infrastructure-neo4j4:model
spring-batch-infrastructure-tests:model
spring-batch-integration:model
spring-batch-samples:model
spring-batch-test:model
spring-build-src:model
projects
spring-batch-core:projects
spring-batch-core-tests:projects
spring-batch-infrastructure:projects
spring-batch-infrastructure-neo4j4:projects
spring-batch-infrastructure-tests:projects
spring-batch-integration:projects
spring-batch-samples:projects
spring-batch-test:projects
spring-build-src:projects
properties
spring-batch-core:properties
spring-batch-core-tests:properties
spring-batch-infrastructure:properties
spring-batch-infrastructure-neo4j4:properties
spring-batch-infrastructure-tests:properties
spring-batch-integration:properties
spring-batch-samples:properties
spring-batch-test:properties
spring-build-src:properties
tasks
spring-batch-core:tasks
spring-batch-core-tests:tasks
spring-batch-infrastructure:tasks
spring-batch-infrastructure-neo4j4:tasks
spring-batch-infrastructure-tests:tasks
spring-batch-integration:tasks
spring-batch-samples:tasks
spring-batch-test:tasks
spring-build-src:tasks
spring-batch-core:cleanEclipse
spring-batch-core-tests:cleanEclipse
spring-batch-infrastructure:cleanEclipse
spring-batch-infrastructure-neo4j4:cleanEclipse
spring-batch-infrastructure-tests:cleanEclipse
spring-batch-integration:cleanEclipse
spring-batch-samples:cleanEclipse
spring-batch-test:cleanEclipse
cleanIdea
spring-batch-core:cleanIdea
spring-batch-core-tests:cleanIdea
spring-batch-infrastructure:cleanIdea
spring-batch-infrastructure-neo4j4:cleanIdea
spring-batch-infrastructure-tests:cleanIdea
spring-batch-integration:cleanIdea
spring-batch-samples:cleanIdea
spring-batch-test:cleanIdea
spring-batch-core:eclipse
spring-batch-core:eclipseClasspath
spring-batch-core:eclipseJdt
spring-batch-core:eclipseProject
spring-batch-core-tests:eclipse
spring-batch-core-tests:eclipseClasspath
spring-batch-core-tests:eclipseJdt
spring-batch-core-tests:eclipseProject
spring-batch-infrastructure:eclipse
spring-batch-infrastructure:eclipseClasspath
spring-batch-infrastructure:eclipseJdt
spring-batch-infrastructure:eclipseProject
spring-batch-infrastructure-neo4j4:eclipse
spring-batch-infrastructure-neo4j4:eclipseClasspath
spring-batch-infrastructure-neo4j4:eclipseJdt
spring-batch-infrastructure-neo4j4:eclipseProject
spring-batch-infrastructure-tests:eclipse
spring-batch-infrastructure-tests:eclipseClasspath
spring-batch-infrastructure-tests:eclipseJdt
spring-batch-infrastructure-tests:eclipseProject
spring-batch-integration:eclipse
spring-batch-integration:eclipseClasspath
spring-batch-integration:eclipseJdt
spring-batch-integration:eclipseProject
spring-batch-samples:eclipse
spring-batch-samples:eclipseClasspath
spring-batch-samples:eclipseJdt
spring-batch-samples:eclipseProject
spring-batch-test:eclipse
spring-batch-test:eclipseClasspath
spring-batch-test:eclipseJdt
spring-batch-test:eclipseProject
idea
ideaModule
ideaProject
ideaWorkspace
spring-batch-core:idea
spring-batch-core:ideaModule
spring-batch-core-tests:idea
spring-batch-core-tests:ideaModule
spring-batch-infrastructure:idea
spring-batch-infrastructure:ideaModule
spring-batch-infrastructure-neo4j4:idea
spring-batch-infrastructure-neo4j4:ideaModule
spring-batch-infrastructure-tests:idea
spring-batch-infrastructure-tests:ideaModule
spring-batch-integration:idea
spring-batch-integration:ideaModule
spring-batch-samples:idea
spring-batch-samples:ideaModule
spring-batch-test:idea
spring-batch-test:ideaModule
check
spring-batch-core:check
spring-batch-core-tests:check
spring-batch-infrastructure:check
spring-batch-infrastructure-neo4j4:check
spring-batch-infrastructure-tests:check
spring-batch-integration:check
spring-batch-samples:check
spring-batch-test:check
spring-build-src:check
clean
spring-batch-core:clean
spring-batch-core-tests:clean
spring-batch-infrastructure:clean
spring-batch-infrastructure-neo4j4:clean
spring-batch-infrastructure-tests:clean
spring-batch-integration:clean
spring-batch-samples:clean
spring-batch-test:clean
spring-build-src:clean
spring-batch-core:test
spring-batch-core-tests:test
spring-batch-infrastructure:test
spring-batch-infrastructure-neo4j4:test
spring-batch-infrastructure-tests:test
spring-batch-integration:test
spring-batch-samples:test
spring-batch-test:test
spring-build-src:test
spring-batch-core:install
spring-batch-core-tests:install
spring-batch-infrastructure:install
spring-batch-infrastructure-neo4j4:install
spring-batch-infrastructure-tests:install
spring-batch-integration:install
spring-batch-samples:install
spring-batch-test:install
sonarRunner
wrapper

View File

@@ -8,8 +8,8 @@ buildscript {
maven { url 'https://repo.spring.io/plugins-release' }
}
dependencies {
classpath 'org.springframework.build.gradle:docbook-reference-plugin:0.2.8'
classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.5'
classpath 'io.spring.gradle:docbook-reference-plugin:0.3.1'
classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7'
classpath 'io.spring.gradle:spring-io-plugin:0.0.5.RELEASE'
}
}
@@ -50,6 +50,7 @@ allprojects {
springDataJpaVersion = '1.6.0.RELEASE'
springDataMongodbVersion = '1.5.0.RELEASE'
springDataNeo4jVersion = '3.1.0.RELEASE'
springDataNeo4j4Version = '4.1.1.RELEASE'
springIntegrationVersion = '4.0.1.RELEASE'
springLdapVersion = '2.0.2.RELEASE'
@@ -62,7 +63,7 @@ allprojects {
commonsLangVersion = '2.6'
derbyVersion = '10.10.1.1'
groovyVersion = '2.3.0'
hamcrestVersion = '1.3'
hamcrestVersion = '1.3'
h2databaseVersion = '1.3.175'
hibernateVersion = '4.2.12.Final'
hibernateValidatorVersion = '4.3.1.Final'
@@ -94,13 +95,14 @@ allprojects {
}
}
subprojects { subproject ->
configure(subprojects - project(":spring-build-src")) { subproject ->
apply plugin: 'java'
apply from: "${rootProject.projectDir}/publish-maven.gradle"
apply plugin: 'jacoco'
apply plugin: 'propdeps-idea'
apply plugin: 'propdeps-eclipse'
apply plugin: 'merge'
jacoco {
toolVersion = "0.7.0.201403182114"
@@ -231,6 +233,18 @@ configure(mainProjects) {
}
}
project("spring-build-src") {
description = "Exposes gradle buildSrc for IDE support"
apply plugin: "groovy"
dependencies {
compile gradleApi()
compile localGroovy()
}
configurations.archives.artifacts.clear()
}
project('spring-batch-core') {
description = 'Spring Batch Core'
@@ -263,7 +277,7 @@ project('spring-batch-core') {
optional "org.slf4j:slf4j-log4j12:$slf4jVersion"
optional "log4j:log4j:$log4jVersion"
provided "javax.batch:javax.batch-api:$javaxBatchApiVersion"
optional "javax.batch:javax.batch-api:$javaxBatchApiVersion"
}
}
@@ -349,6 +363,17 @@ project('spring-batch-infrastructure') {
}
}
project('spring-batch-infrastructure-neo4j4') {
merge.into = project(':spring-batch-infrastructure')
dependencies {
optional "org.springframework.data:spring-data-neo4j:$springDataNeo4j4Version"
testCompile "org.mockito:mockito-core:$mockitoVersion"
testCompile "junit:junit:${junitVersion}"
}
}
project('spring-batch-core-tests') {
description = 'Spring Batch Core Tests'
project.tasks.findByPath("artifactoryPublish")?.enabled = false
@@ -504,6 +529,8 @@ project('spring-batch-integration') {
optional "log4j:log4j:1.2.14"
optional "org.springframework.integration:spring-integration-jms:$springIntegrationVersion"
optional "org.springframework:spring-jms:$springVersion"
optional "javax.batch:javax.batch-api:$javaxBatchApiVersion"
}
}
@@ -573,13 +600,13 @@ project('spring-batch-samples') {
optional "org.springframework.amqp:spring-rabbit:$springAmqpVersion"
optional "javax.inject:javax.inject:1"
optional "javax.batch:javax.batch-api:$javaxBatchApiVersion"
}
}
apply plugin: 'docbook-reference'
reference {
//sourceDir = file('src/reference/docbook')
sourceDir = file('src/site/docbook/reference')
}
@@ -793,5 +820,5 @@ or specify the Gradle property `TCK_HOME`, e.g: ./gradlew runTck -PTCK_HOME=/pat
task wrapper(type: Wrapper) {
description = 'Generates gradlew[.bat] scripts'
gradleVersion = '1.11'
gradleVersion = '2.12'
}

View File

@@ -0,0 +1,159 @@
/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.build.gradle
import org.gradle.api.Project
import org.gradle.api.Action
import org.gradle.api.Plugin
import org.gradle.api.invocation.Gradle
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.maven.Conf2ScopeMapping
import org.gradle.api.plugins.MavenPlugin
import org.gradle.plugins.ide.eclipse.EclipsePlugin
import org.gradle.plugins.ide.idea.IdeaPlugin
/**
* Gradle plugin that allows projects to merged together. Primarily developed to
* allow Spring to support multiple incompatible versions of third-party
* dependencies (for example Hibernate v3 and v4).
* <p>
* The 'merge' extension should be used to define how projects are merged, for example:
* <pre class="code">
* configure(subprojects) {
* apply plugin: MergePlugin
* }
*
* project("myproject") {
* }
*
* project("myproject-extra") {
* merge.into = project("myproject")
* }
* </pre>
* <p>
* This plugin adds two new configurations:
* <ul>
* <li>merging - Contains the projects being merged into this project<li>
* <li>runtimeMerge - Contains all dependencies that are merge projects. These are used
* to allow an IDE to reference merge projects.</li>
* <ul>
*
* @author Rob Winch
* @author Phillip Webb
*/
class MergePlugin implements Plugin<Project> {
private static boolean attachedProjectsEvaluated;
public void apply(Project project) {
project.plugins.apply(MavenPlugin)
project.plugins.apply(EclipsePlugin)
project.plugins.apply(IdeaPlugin)
MergeModel model = project.extensions.create("merge", MergeModel)
project.configurations.create("merging")
Configuration runtimeMerge = project.configurations.create("runtimeMerge")
// Ensure the IDE can reference merged projects
project.eclipse.classpath.plusConfigurations += [ runtimeMerge ]
project.idea.module.scopes.PROVIDED.plus += [ runtimeMerge ]
// Hook to perform the actual merge logic
project.afterEvaluate{
if (it.merge.into != null) {
setup(it)
}
}
// Hook to build runtimeMerge dependencies
if (!attachedProjectsEvaluated) {
project.gradle.projectsEvaluated{
postProcessProjects(it)
}
attachedProjectsEvaluated = true;
}
}
private void setup(Project project) {
project.merge.into.dependencies.add("merging", project)
project.dependencies.add("provided", project.merge.into.sourceSets.main.output)
project.dependencies.add("runtimeMerge", project.merge.into)
setupTaskDependencies(project)
setupMaven(project)
}
private void setupTaskDependencies(Project project) {
// invoking a task will invoke the task with the same name on 'into' project
["sourcesJar", "jar", "javadocJar", "javadoc", "install", "artifactoryPublish"].each {
def task = project.tasks.findByPath(it)
if (task) {
task.enabled = false
task.dependsOn(project.merge.into.tasks.findByPath(it))
}
}
// update 'into' project artifacts to contain the source artifact contents
project.merge.into.sourcesJar.from(project.sourcesJar.source)
project.merge.into.jar.from(project.sourceSets.main.output)
project.merge.into.javadoc {
source += project.javadoc.source
classpath += project.javadoc.classpath
}
}
private void setupMaven(Project project) {
project.configurations.each { configuration ->
Conf2ScopeMapping mapping = project.conf2ScopeMappings.getMapping([configuration])
if (mapping.scope) {
Configuration intoConfiguration = project.merge.into.configurations.create(
project.name + "-" + configuration.name)
configuration.excludeRules.each {
configuration.exclude([
(ExcludeRule.GROUP_KEY) : it.group,
(ExcludeRule.MODULE_KEY) : it.module])
}
configuration.dependencies.each {
def intoCompile = project.merge.into.configurations.getByName("compile")
// Protect against changing a compile scope dependency (SPR-10218)
if (!intoCompile.dependencies.contains(it)) {
intoConfiguration.dependencies.add(it)
}
}
def index = project.parent.childProjects.findIndexOf {p -> p.getValue() == project}
project.merge.into.install.repositories.mavenInstaller.pom.scopeMappings.addMapping(
mapping.priority + 100 + index, intoConfiguration, mapping.scope)
}
}
}
private postProcessProjects(Gradle gradle) {
gradle.rootProject.subprojects(new Action<Project>() {
public void execute(Project project) {
project.configurations.getByName("runtime").allDependencies.withType(ProjectDependency).each{
Configuration dependsOnMergedFrom = it.dependencyProject.configurations.getByName("merging");
dependsOnMergedFrom.dependencies.each{ dep ->
project.dependencies.add("runtimeMerge", dep.dependencyProject)
}
}
}
});
}
}
class MergeModel {
Project into;
}

View File

@@ -0,0 +1 @@
implementation-class=org.springframework.build.gradle.MergePlugin

Binary file not shown.

View File

@@ -1,6 +1,6 @@
#Thu May 08 10:59:52 CDT 2014
#Mon Apr 18 18:28:38 CDT 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=http\://services.gradle.org/distributions/gradle-1.11-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-bin.zip

View File

@@ -3,7 +3,11 @@ rootProject.name = 'spring-batch'
include 'spring-batch-core'
include 'spring-batch-core-tests'
include 'spring-batch-infrastructure'
include 'spring-batch-infrastructure-neo4j4'
include 'spring-batch-infrastructure-tests'
include 'spring-batch-test'
include 'spring-batch-integration'
include 'spring-batch-samples'
include "buildSrc"
rootProject.children.find{ it.name == "buildSrc" }.name = "spring-build-src"

View File

@@ -0,0 +1,47 @@
/*
* Copyright 2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.data;
import java.util.ArrayList;
import java.util.Iterator;
/**
* <p>
* Extensions of the {@link AbstractNeo4jItemReader} intended for use with versions of
* Spring Data Neo4J &gt; 4. Conversions of the results are done using logic based on the
* target type.
* </p>
*
* @author Michael Minella
* @author Vince Bickers
* @since 3.0.7
*/
public class Neo4j4ItemReader<T> extends AbstractNeo4jItemReader {
@Override
protected Iterator<T> doPageRead() {
Iterable queryResults = getTemplate().queryForObjects(
getTargetType(), generateLimitCypherQuery(), getParameterValues());
if(queryResults != null) {
return queryResults.iterator();
}
else {
return new ArrayList<T>().iterator();
}
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright 2013-2014 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.data;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.data.neo4j.template.Neo4jOperations;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.when;
public class Neo4j4ItemReaderTests {
private Neo4j4ItemReader<String> reader;
@Mock
private Neo4jOperations template;
@Mock
private Iterable<String> result;
@Before
public void setUp() throws Exception {
reader = new Neo4j4ItemReader<String>();
MockitoAnnotations.initMocks(this);
reader.setTemplate(template);
reader.setTargetType(String.class);
reader.setStartStatement("n=node(*)");
reader.setReturnStatement("*");
reader.setOrderByStatement("n.age");
reader.setPageSize(50);
reader.afterPropertiesSet();
}
@Test
public void testAfterPropertiesSet() throws Exception {
reader = new Neo4j4ItemReader<String>();
try {
reader.afterPropertiesSet();
fail("Template was not set but exception was not thrown.");
} catch (IllegalStateException iae) {
assertEquals("A Neo4JOperations implementation is required", iae.getMessage());
} catch (Throwable t) {
fail("Wrong exception was thrown:" + t);
}
reader.setTemplate(template);
try {
reader.afterPropertiesSet();
fail("type was not set but exception was not thrown.");
} catch (IllegalStateException iae) {
assertEquals("The type to be returned is required", iae.getMessage());
} catch (Throwable t) {
fail("Wrong exception was thrown:" + t);
}
reader.setTargetType(String.class);
try {
reader.afterPropertiesSet();
fail("START was not set but exception was not thrown.");
} catch (IllegalStateException iae) {
assertEquals("A START statement is required", iae.getMessage());
} catch (Throwable t) {
fail("Wrong exception was thrown:" + t);
}
reader.setStartStatement("n=node(*)");
try {
reader.afterPropertiesSet();
fail("RETURN was not set but exception was not thrown.");
} catch (IllegalStateException iae) {
assertEquals("A RETURN statement is required", iae.getMessage());
} catch (Throwable t) {
fail("Wrong exception was thrown:" + t);
}
reader.setReturnStatement("n.name, n.phone");
try {
reader.afterPropertiesSet();
fail("ORDER BY was not set but exception was not thrown.");
} catch (IllegalStateException iae) {
assertEquals("A ORDER BY statement is required", iae.getMessage());
} catch (Throwable t) {
fail("Wrong exception was thrown:" + t);
}
reader.setOrderByStatement("n.age");
reader.afterPropertiesSet();
}
@SuppressWarnings("unchecked")
@Test
public void testNullResults() {
ArgumentCaptor<String> query = ArgumentCaptor.forClass(String.class);
when(template.queryForObjects(eq(String.class), query.capture(), (Map<String, Object>) isNull())).thenReturn(null);
assertFalse(reader.doPageRead().hasNext());
assertEquals("START n=node(*) RETURN * ORDER BY n.age SKIP 0 LIMIT 50", query.getValue());
}
@SuppressWarnings("unchecked")
@Test
public void testNoResults() {
ArgumentCaptor<String> query = ArgumentCaptor.forClass(String.class);
when(template.queryForObjects(eq(String.class), query.capture(), (Map<String, Object>) isNull())).thenReturn(result);
when(result.iterator()).thenReturn(Collections.<String>emptyIterator());
assertFalse(reader.doPageRead().hasNext());
assertEquals("START n=node(*) RETURN * ORDER BY n.age SKIP 0 LIMIT 50", query.getValue());
}
@SuppressWarnings("serial")
@Test
public void testResultsWithMatchAndWhere() throws Exception {
reader.setMatchStatement("n -- m");
reader.setWhereStatement("has(n.name)");
reader.setReturnStatement("m");
reader.afterPropertiesSet();
when(template.queryForObjects(String.class, "START n=node(*) MATCH n -- m WHERE has(n.name) RETURN m ORDER BY n.age SKIP 0 LIMIT 50", null)).thenReturn(result);
when(result.iterator()).thenReturn(Arrays.asList("foo", "bar", "baz").iterator());
assertTrue(reader.doPageRead().hasNext());
}
@SuppressWarnings("serial")
@Test
public void testResultsWithMatchAndWhereWithParameters() throws Exception {
Map<String, Object> params = new HashMap<String, Object>();
params.put("foo", "bar");
reader.setParameterValues(params);
reader.setMatchStatement("n -- m");
reader.setWhereStatement("has(n.name)");
reader.setReturnStatement("m");
reader.afterPropertiesSet();
when(template.queryForObjects(String.class, "START n=node(*) MATCH n -- m WHERE has(n.name) RETURN m ORDER BY n.age SKIP 0 LIMIT 50", params)).thenReturn(result);
when(result.iterator()).thenReturn(Arrays.asList("foo", "bar", "baz").iterator());
assertTrue(reader.doPageRead().hasNext());
}
}

View File

@@ -0,0 +1,206 @@
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.item.data;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.neo4j.template.Neo4jOperations;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* <p>
* Restartable {@link ItemReader} that reads objects from the graph database Neo4j
* via a paging technique.
* </p>
*
* <p>
* It executes cypher queries built from the statement fragments provided to
* retrieve the requested data. The query is executed using paged requests of
* a size specified in {@link #setPageSize(int)}. Additional pages are requested
* as needed when the {@link #read()} method is called. On restart, the reader
* will begin again at the same number item it left off at.
* </p>
*
* <p>
* Performance is dependent on your Neo4J configuration (embedded or remote) as
* well as page size. Setting a fairly large page size and using a commit
* interval that matches the page size should provide better performance.
* </p>
*
* <p>
* This implementation is thread-safe between calls to
* {@link #open(org.springframework.batch.item.ExecutionContext)}, however you
* should set <code>saveState=false</code> if used in a multi-threaded
* environment (no restart available).
* </p>
*
* @author Michael Minella
* @since 3.07
*/
public abstract class AbstractNeo4jItemReader<T> extends
AbstractPaginatedDataItemReader<T> implements InitializingBean {
protected Log logger = LogFactory.getLog(getClass());
private Neo4jOperations template;
private String startStatement;
private String returnStatement;
private String matchStatement;
private String whereStatement;
private String orderByStatement;
private Class<T> targetType;
private Map<String, Object> parameterValues;
/**
* Optional parameters to be used in the cypher query.
*
* @param parameterValues the parameter values to be used in the cypher query
*/
public void setParameterValues(Map<String, Object> parameterValues) {
this.parameterValues = parameterValues;
}
protected final Map<String, Object> getParameterValues() {
return this.parameterValues;
}
/**
* The start segment of the cypher query. START is prepended
* to the statement provided and should <em>not</em> be
* included.
*
* @param startStatement the start fragment of the cypher query.
*/
public void setStartStatement(String startStatement) {
this.startStatement = startStatement;
}
/**
* The return statement of the cypher query. RETURN is prepended
* to the statement provided and should <em>not</em> be
* included
*
* @param returnStatement the return fragment of the cypher query.
*/
public void setReturnStatement(String returnStatement) {
this.returnStatement = returnStatement;
}
/**
* An optional match fragment of the cypher query. MATCH is
* prepended to the statement provided and should <em>not</em>
* be included.
*
* @param matchStatement the match fragment of the cypher query
*/
public void setMatchStatement(String matchStatement) {
this.matchStatement = matchStatement;
}
/**
* An optional where fragment of the cypher query. WHERE is
* prepended to the statement provided and should <em>not</em>
* be included.
*
* @param whereStatement where fragment of the cypher query
*/
public void setWhereStatement(String whereStatement) {
this.whereStatement = whereStatement;
}
/**
* A list of properties to order the results by. This is
* required so that subsequent page requests pull back the
* segment of results correctly. ORDER BY is prepended to
* the statement provided and should <em>not</em> be included.
*
* @param orderByStatement order by fragment of the cypher query.
*/
public void setOrderByStatement(String orderByStatement) {
this.orderByStatement = orderByStatement;
}
/**
* Used to perform operations against the Neo4J database.
*
* @param template the Neo4jOperations instance to use
* @see Neo4jOperations
*/
public void setTemplate(Neo4jOperations template) {
this.template = template;
}
protected final Neo4jOperations getTemplate() {
return this.template;
}
/**
* The object type to be returned from each call to {@link #read()}
*
* @param targetType the type of object to return.
*/
public void setTargetType(Class<T> targetType) {
this.targetType = targetType;
}
protected final Class<T> getTargetType() {
return this.targetType;
}
protected String generateLimitCypherQuery() {
StringBuilder query = new StringBuilder();
query.append("START ").append(startStatement);
query.append(matchStatement != null ? " MATCH " + matchStatement : "");
query.append(whereStatement != null ? " WHERE " + whereStatement : "");
query.append(" RETURN ").append(returnStatement);
query.append(" ORDER BY ").append(orderByStatement);
query.append(" SKIP " + (pageSize * page));
query.append(" LIMIT " + pageSize);
String resultingQuery = query.toString();
if (logger.isDebugEnabled()) {
logger.debug(resultingQuery);
}
return resultingQuery;
}
/**
* Checks mandatory properties
*
* @see InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(template != null, "A Neo4JOperations implementation is required");
Assert.state(targetType != null, "The type to be returned is required");
Assert.state(StringUtils.hasText(startStatement), "A START statement is required");
Assert.state(StringUtils.hasText(returnStatement), "A RETURN statement is required");
Assert.state(StringUtils.hasText(orderByStatement), "A ORDER BY statement is required");
}
}

View File

@@ -22,153 +22,32 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.neo4j.conversion.DefaultConverter;
import org.springframework.data.neo4j.conversion.Result;
import org.springframework.data.neo4j.conversion.ResultConverter;
import org.springframework.data.neo4j.template.Neo4jOperations;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
/**
* <p>
* Restartable {@link ItemReader} that reads objects from the graph database Neo4j
* via a paging technique.
* </p>
*
* <p>
* It executes cypher queries built from the statement fragments provided to
* retrieve the requested data. The query is executed using paged requests of
* a size specified in {@link #setPageSize(int)}. Additional pages are requested
* as needed when the {@link #read()} method is called. On restart, the reader
* will begin again at the same number item it left off at.
* </p>
*
* <p>
* Performance is dependent on your Neo4J configuration (embedded or remote) as
* well as page size. Setting a fairly large page size and using a commit
* interval that matches the page size should provide better performance.
* </p>
*
* <p>
* This implementation is thread-safe between calls to
* {@link #open(org.springframework.batch.item.ExecutionContext)}, however you
* should set <code>saveState=false</code> if used in a multi-threaded
* environment (no restart available).
* Extensions of the {@link AbstractNeo4jItemReader} intended for use with versions of
* Spring Data Neo4J &lt; 4. Conversions of the results are done using an external
* {@link ResultConverter}.
* </p>
*
* @author Michael Minella
*
* @see org.springframework.batch.item.data.Neo4j4ItemReader
*/
public class Neo4jItemReader<T> extends AbstractPaginatedDataItemReader<T> implements
InitializingBean {
public class Neo4jItemReader<T> extends AbstractNeo4jItemReader {
protected Log logger = LogFactory.getLog(getClass());
private Neo4jOperations template;
private String startStatement;
private String returnStatement;
private String matchStatement;
private String whereStatement;
private String orderByStatement;
private Class<T> targetType;
private Map<String, Object> parameterValues;
private ResultConverter<Map<String, Object>, T> resultConverter;
public Neo4jItemReader() {
setName(ClassUtils.getShortName(Neo4jItemReader.class));
}
/**
* Optional parameters to be used in the cypher query.
*
* @param parameterValues the parameter values to be used in the cypher query
*/
public void setParameterValues(Map<String, Object> parameterValues) {
this.parameterValues = parameterValues;
}
/**
* The start segment of the cypher query. START is prepended
* to the statement provided and should <em>not</em> be
* included.
*
* @param startStatement the start fragment of the cypher query.
*/
public void setStartStatement(String startStatement) {
this.startStatement = startStatement;
}
/**
* The return statement of the cypher query. RETURN is prepended
* to the statement provided and should <em>not</em> be
* included
*
* @param returnStatement the return fragment of the cypher query.
*/
public void setReturnStatement(String returnStatement) {
this.returnStatement = returnStatement;
}
/**
* An optional match fragment of the cypher query. MATCH is
* prepended to the statement provided and should <em>not</em>
* be included.
*
* @param matchStatement the match fragment of the cypher query
*/
public void setMatchStatement(String matchStatement) {
this.matchStatement = matchStatement;
}
/**
* An optional where fragment of the cypher query. WHERE is
* prepended to the statement provided and should <em>not</em>
* be included.
*
* @param whereStatement where fragment of the cypher query
*/
public void setWhereStatement(String whereStatement) {
this.whereStatement = whereStatement;
}
/**
* A list of properties to order the results by. This is
* required so that subsequent page requests pull back the
* segment of results correctly. ORDER BY is prepended to
* the statement provided and should <em>not</em> be included.
*
* @param orderByStatement order by fragment of the cypher query.
*/
public void setOrderByStatement(String orderByStatement) {
this.orderByStatement = orderByStatement;
}
/**
* Used to perform operations against the Neo4J database.
*
* @param template the Neo4jOperations instance to use
* @see Neo4jOperations
*/
public void setTemplate(Neo4jOperations template) {
this.template = template;
}
/**
* The object type to be returned from each call to {@link #read()}
*
* @param targetType the type of object to return.
*/
public void setTargetType(Class<T> targetType) {
this.targetType = targetType;
}
/**
* Set the converter used to convert node to the targetType. By
* default, {@link DefaultConverter} is used.
@@ -181,53 +60,19 @@ InitializingBean {
@Override
protected Iterator<T> doPageRead() {
Result<Map<String, Object>> queryResults = template.query(
generateLimitCypherQuery(), parameterValues);
Result<Map<String, Object>> queryResults = getTemplate().query(
generateLimitCypherQuery(), getParameterValues());
if(queryResults != null) {
if (resultConverter != null) {
return queryResults.to(targetType, resultConverter).iterator();
return queryResults.to(getTargetType(), resultConverter).iterator();
}
else {
return queryResults.to(targetType).iterator();
return queryResults.to(getTargetType()).iterator();
}
}
else {
return new ArrayList<T>().iterator();
}
}
private String generateLimitCypherQuery() {
StringBuilder query = new StringBuilder();
query.append("START ").append(startStatement);
query.append(matchStatement != null ? " MATCH " + matchStatement : "");
query.append(whereStatement != null ? " WHERE " + whereStatement : "");
query.append(" RETURN ").append(returnStatement);
query.append(" ORDER BY ").append(orderByStatement);
query.append(" SKIP " + (pageSize * page));
query.append(" LIMIT " + pageSize);
String resultingQuery = query.toString();
if (logger.isDebugEnabled()) {
logger.debug(resultingQuery);
}
return resultingQuery;
}
/**
* Checks mandatory properties
*
* @see InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(template != null, "A Neo4JOperations implementation is required");
Assert.state(targetType != null, "The type to be returned is required");
Assert.state(StringUtils.hasText(startStatement), "A START statement is required");
Assert.state(StringUtils.hasText(returnStatement), "A RETURN statement is required");
Assert.state(StringUtils.hasText(orderByStatement), "A ORDER BY statement is required");
}
}