Added builder for Neo4jItemReader

resolves BATCH-2610
This commit is contained in:
Glenn Renfro
2017-05-16 17:10:26 -04:00
committed by Michael Minella
parent 61f6cd5c0a
commit f2fcbfe2a8
5 changed files with 515 additions and 2 deletions

View File

@@ -147,6 +147,10 @@ public abstract class AbstractNeo4jItemReader<T> extends
return sessionFactory;
}
/**
* Establish the session factory for the reader.
* @param sessionFactory the factory to use for the reader.
*/
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}

View File

@@ -18,6 +18,7 @@ package org.springframework.batch.item.data;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStreamReader;
import org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader;
import org.springframework.util.Assert;
import java.util.Iterator;
@@ -27,6 +28,7 @@ import java.util.Iterator;
* semantics required for restartability based on those facilities.
*
* @author Michael Minella
* @author Glenn Renfro
* @since 2.2
* @param <T> Type of item to be read
*/
@@ -44,9 +46,10 @@ AbstractItemCountingItemStreamItemReader<T> {
/**
* The number of items to be read with each page.
*
* @param pageSize the number of items
* @param pageSize the number of items. pageSize must be greater than zero.
*/
public void setPageSize(int pageSize) {
Assert.isTrue(pageSize > 0, "pageSize must be greater than zero");
this.pageSize = pageSize;
}

View File

@@ -0,0 +1,213 @@
/*
* Copyright 2017 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.builder;
import java.util.Map;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.batch.item.builder.AbstractItemCountingItemStreamItemReaderBuilder;
import org.springframework.batch.item.data.Neo4jItemReader;
import org.springframework.util.Assert;
/**
* A builder for the {@link Neo4jItemReader}.
*
* @author Glenn Renfro
* @since 4.0
* @see Neo4jItemReader
*/
public class Neo4jItemReaderBuilder<T>
extends AbstractItemCountingItemStreamItemReaderBuilder<Neo4jItemReaderBuilder<T>> {
private SessionFactory sessionFactory;
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 int pageSize = 10;
/**
* Establish the session factory for the reader.
* @param sessionFactory the factory to use for the reader.
* @return this instance for method chaining
* @see Neo4jItemReader#setSessionFactory(SessionFactory)
*/
public Neo4jItemReaderBuilder<T> sessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
return this;
}
/**
* The number of items to be read with each page.
*
* @param pageSize the number of items
* @return this instance for method chaining
* @see Neo4jItemReader#setPageSize(int)
*/
public Neo4jItemReaderBuilder<T> pageSize(int pageSize) {
this.pageSize = pageSize;
return this;
}
/**
* Optional parameters to be used in the cypher query.
*
* @param parameterValues the parameter values to be used in the cypher query
* @return this instance for method chaining
* @see Neo4jItemReader#setParameterValues(Map)
*/
public Neo4jItemReaderBuilder<T> parameterValues(Map<String, Object> parameterValues) {
this.parameterValues = parameterValues;
return this;
}
/**
* 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.
* @return this instance for method chaining
* @see Neo4jItemReader#setStartStatement(String)
*/
public Neo4jItemReaderBuilder<T> startStatement(String startStatement) {
this.startStatement = startStatement;
return this;
}
/**
* 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.
* @return this instance for method chaining
* @see Neo4jItemReader#setReturnStatement(String)
*/
public Neo4jItemReaderBuilder<T> returnStatement(String returnStatement) {
this.returnStatement = returnStatement;
return this;
}
/**
* 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
* @return this instance for method chaining
* @see Neo4jItemReader#setMatchStatement(String)
*/
public Neo4jItemReaderBuilder<T> matchStatement(String matchStatement) {
this.matchStatement = matchStatement;
return this;
}
/**
* 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
* @return this instance for method chaining
* @see Neo4jItemReader#setWhereStatement(String)
*/
public Neo4jItemReaderBuilder<T> whereStatement(String whereStatement) {
this.whereStatement = whereStatement;
return this;
}
/**
* 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.
* @return this instance for method chaining
* @see Neo4jItemReader#setOrderByStatement(String)
*/
public Neo4jItemReaderBuilder<T> orderByStatement(String orderByStatement) {
this.orderByStatement = orderByStatement;
return this;
}
/**
* The object type to be returned from each call to {@link Neo4jItemReader#read()}
*
* @param targetType the type of object to return.
* @return this instance for method chaining
* @see Neo4jItemReader#setTargetType(Class)
*/
public Neo4jItemReaderBuilder<T> targetType(Class<T> targetType) {
this.targetType = targetType;
return this;
}
/**
* Returns a fully constructed {@link Neo4jItemReader}.
*
* @return a new {@link Neo4jItemReader}
*/
public Neo4jItemReader<T> build() {
if (this.saveState) {
Assert.hasText(this.name, "A name is required when saveState is set to true");
}
Assert.notNull(this.sessionFactory, "sessionFactory is required.");
Assert.notNull(this.targetType, "targetType is required.");
Assert.hasText(this.startStatement, "startStatement is required.");
Assert.hasText(this.returnStatement, "returnStatement is required.");
Assert.hasText(this.orderByStatement, "orderByStatement is required.");
Assert.isTrue(this.pageSize > 0, "pageSize must be greater than zero");
Assert.isTrue(this.maxItemCount > 0, "maxItemCount must be greater than zero");
Assert.isTrue(this.maxItemCount > this.currentItemCount , "maxItemCount must be greater than currentItemCount");
Neo4jItemReader<T> reader = new Neo4jItemReader<>();
reader.setMatchStatement(this.matchStatement);
reader.setOrderByStatement(this.orderByStatement);
reader.setPageSize(this.pageSize);
reader.setParameterValues(this.parameterValues);
reader.setSessionFactory(this.sessionFactory);
reader.setTargetType(this.targetType);
reader.setStartStatement(this.startStatement);
reader.setReturnStatement(this.returnStatement);
reader.setWhereStatement(this.whereStatement);
reader.setName(this.name);
reader.setSaveState(this.saveState);
reader.setCurrentItemCount(this.currentItemCount);
reader.setMaxItemCount(this.maxItemCount);
return reader;
}
}

View File

@@ -32,6 +32,7 @@ import org.springframework.util.Assert;
* Subclasses are inherently <b>not</b> thread-safe.
*
* @author Robert Kasanicky
* @author Glenn Renfro
*/
public abstract class AbstractItemCountingItemStreamItemReader<T> extends AbstractItemStreamItemReader<T> {
@@ -119,9 +120,10 @@ public abstract class AbstractItemCountingItemStreamItemReader<T> extends Abstra
*
* @see #setName(String)
*
* @param count the value of the maximum item count
* @param count the value of the maximum item count. count must be greater than zero.
*/
public void setMaxItemCount(int count) {
Assert.isTrue(count > 0, "count must be greater than zero");
this.maxItemCount = count;
}

View File

@@ -0,0 +1,291 @@
/*
* Copyright 2017 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.builder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.batch.item.data.Neo4jItemReader;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.when;
/**
* @author Glenn Renfro
*/
public class Neo4jItemReaderBuilderTests {
@Mock
private Iterable<String> result;
@Mock
private SessionFactory sessionFactory;
@Mock
private Session session;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
@Test
public void testFullyQualifiedItemReader() throws Exception {
Neo4jItemReader<String> itemReader = new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.orderByStatement("n.age")
.pageSize(50).name("bar")
.matchStatement("n -- m")
.whereStatement("has(n.name)")
.returnStatement("m").build();
when(this.sessionFactory.openSession()).thenReturn(this.session);
when(this.session.query(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());
assertEquals("The expected value was not returned by reader.", "foo", itemReader.read());
assertEquals("The expected value was not returned by reader.", "bar", itemReader.read());
assertEquals("The expected value was not returned by reader.", "baz", itemReader.read());
}
@Test
public void testCurrentSize() throws Exception {
Neo4jItemReader<String> itemReader = new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.orderByStatement("n.age")
.pageSize(50).name("bar")
.returnStatement("m")
.currentItemCount(0)
.maxItemCount(1)
.build();
when(this.sessionFactory.openSession()).thenReturn(this.session);
when(this.session.query(String.class, "START n=node(*) RETURN m ORDER BY n.age SKIP 0 LIMIT 50", null))
.thenReturn(result);
when(result.iterator()).thenReturn(Arrays.asList("foo", "bar", "baz").iterator());
assertEquals("The expected value was not returned by reader.", "foo", itemReader.read());
assertNull("The expected value was not should be null.", itemReader.read());
}
@Test
public void testResultsWithMatchAndWhereWithParametersWithSession() throws Exception {
Map<String, Object> params = new HashMap<>();
params.put("foo", "bar");
Neo4jItemReader<String> itemReader = new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.returnStatement("*")
.orderByStatement("n.age")
.pageSize(50)
.name("foo")
.parameterValues(params)
.matchStatement("n -- m")
.whereStatement("has(n.name)")
.returnStatement("m")
.build();
when(this.sessionFactory.openSession()).thenReturn(this.session);
when(this.session.query(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());
assertEquals("The expected value was not returned by reader.", "foo", itemReader.read());
}
@Test
public void testNoSessionFactory() {
try {
new Neo4jItemReaderBuilder<String>()
.targetType(String.class)
.startStatement("n=node(*)")
.returnStatement("*")
.orderByStatement("n.age")
.pageSize(50)
.name("bar").build();
fail("IllegalArgumentException should have been thrown");
}
catch (IllegalArgumentException iae) {
assertEquals("IllegalArgumentException message did not match the expected result.",
"sessionFactory is required.", iae.getMessage());
}
}
@Test
public void testZeroPageSize() {
validateExceptionMessage(new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.returnStatement("*")
.orderByStatement("n.age")
.pageSize(0)
.name("foo")
.matchStatement("n -- m")
.whereStatement("has(n.name)")
.returnStatement("m"),
"pageSize must be greater than zero");
}
@Test
public void testZeroMaxItemCount() {
validateExceptionMessage(new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.returnStatement("*")
.orderByStatement("n.age")
.pageSize(5)
.maxItemCount(0)
.name("foo")
.matchStatement("n -- m")
.whereStatement("has(n.name)")
.returnStatement("m"),
"maxItemCount must be greater than zero");
}
@Test
public void testCurrentItemCountGreaterThanMaxItemCount() {
validateExceptionMessage(new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.returnStatement("*")
.orderByStatement("n.age")
.pageSize(5)
.maxItemCount(5)
.currentItemCount(6)
.name("foo")
.matchStatement("n -- m")
.whereStatement("has(n.name)")
.returnStatement("m"),
"maxItemCount must be greater than currentItemCount");
}
@Test
public void testNullName() {
validateExceptionMessage(
new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.returnStatement("*")
.orderByStatement("n.age")
.pageSize(50),
"A name is required when saveState is set to true");
// tests that name is not required if saveState is set to false.
new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.returnStatement("*")
.orderByStatement("n.age")
.saveState(false)
.pageSize(50)
.build();
}
@Test
public void testNullTargetType() {
validateExceptionMessage(
new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.startStatement("n=node(*)")
.returnStatement("*")
.orderByStatement("n.age")
.pageSize(50)
.name("bar")
.matchStatement("n -- m")
.whereStatement("has(n.name)")
.returnStatement("m"),
"targetType is required.");
}
@Test
public void testNullStartStatement() {
validateExceptionMessage(
new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.returnStatement("*")
.orderByStatement("n.age")
.pageSize(50).name("bar")
.matchStatement("n -- m")
.whereStatement("has(n.name)")
.returnStatement("m"),
"startStatement is required.");
}
@Test
public void testNullReturnStatement() {
validateExceptionMessage(new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.orderByStatement("n.age")
.pageSize(50).name("bar")
.matchStatement("n -- m")
.whereStatement("has(n.name)"), "returnStatement is required.");
}
@Test
public void testNullOrderByStatement() {
validateExceptionMessage(
new Neo4jItemReaderBuilder<String>()
.sessionFactory(this.sessionFactory)
.targetType(String.class)
.startStatement("n=node(*)")
.returnStatement("*")
.pageSize(50)
.name("bar")
.matchStatement("n -- m")
.whereStatement("has(n.name)")
.returnStatement("m"),
"orderByStatement is required.");
}
private void validateExceptionMessage(Neo4jItemReaderBuilder<?> builder, String message) {
try {
builder.build();
fail("IllegalArgumentException should have been thrown");
}
catch (IllegalArgumentException iae) {
assertEquals("IllegalArgumentException message did not match the expected result.", message,
iae.getMessage());
}
}
}