Add module for Neo4j

This initial import is a copy of the code
from Spring Batch v4.3.3.
This commit is contained in:
Mahmoud Ben Hassine
2021-09-01 10:03:38 +02:00
parent e1d5fc5a4a
commit edae1738c5
12 changed files with 1649 additions and 0 deletions

View File

@@ -0,0 +1,224 @@
/*
* Copyright 2012-2021 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
*
* https://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.extensions.neo4j;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.data.AbstractPaginatedDataItemReader;
import org.springframework.beans.factory.InitializingBean;
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
* @author Mahmoud Ben Hassine
*/
public class Neo4jItemReader<T> extends AbstractPaginatedDataItemReader<T> implements InitializingBean {
protected Log logger = LogFactory.getLog(getClass());
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;
/**
* 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;
}
protected SessionFactory getSessionFactory() {
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;
}
/**
* 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(128);
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(sessionFactory != null,"A SessionFactory 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");
}
@SuppressWarnings("unchecked")
@Override
protected Iterator<T> doPageRead() {
Session session = getSessionFactory().openSession();
Iterable<T> queryResults = session.query(getTargetType(),
generateLimitCypherQuery(),
getParameterValues());
if(queryResults != null) {
return queryResults.iterator();
}
else {
return new ArrayList<T>().iterator();
}
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2012-2021 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
*
* https://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.extensions.neo4j;
import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
/**
* <p>
* A {@link ItemWriter} implementation that writes to a Neo4j database.
* </p>
*
* <p>
* This writer is thread-safe once all properties are set (normal singleton
* behavior) so it can be used in multiple concurrent transactions.
* </p>
*
* @author Michael Minella
* @author Glenn Renfro
* @author Mahmoud Ben Hassine
*
*/
public class Neo4jItemWriter<T> implements ItemWriter<T>, InitializingBean {
protected static final Log logger = LogFactory
.getLog(Neo4jItemWriter.class);
private boolean delete = false;
private SessionFactory sessionFactory;
/**
* Boolean flag indicating whether the writer should save or delete the item at write
* time.
* @param delete true if write should delete item, false if item should be saved.
* Default is false.
*/
public void setDelete(boolean delete) {
this.delete = delete;
}
/**
* Establish the session factory that will be used to create {@link Session} instances
* for interacting with Neo4j.
* @param sessionFactory sessionFactory to be used.
*/
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
/**
* Checks mandatory properties
*
* @see InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
Assert.state(this.sessionFactory != null,
"A SessionFactory is required");
}
/**
* Write all items to the data store.
*
* @see org.springframework.batch.item.ItemWriter#write(java.util.List)
*/
@Override
public void write(List<? extends T> items) throws Exception {
if(!CollectionUtils.isEmpty(items)) {
doWrite(items);
}
}
/**
* Performs the actual write using the template. This can be overridden by
* a subclass if necessary.
*
* @param items the list of items to be persisted.
*/
protected void doWrite(List<? extends T> items) {
if(delete) {
delete(items);
}
else {
save(items);
}
}
private void delete(List<? extends T> items) {
Session session = this.sessionFactory.openSession();
for(T item : items) {
session.delete(item);
}
}
private void save(List<? extends T> items) {
Session session = this.sessionFactory.openSession();
for (T item : items) {
session.save(item);
}
}
}

View File

@@ -0,0 +1,273 @@
/*
* Copyright 2017-2021 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
*
* https://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.extensions.neo4j.builder;
import java.util.Map;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.batch.item.data.Neo4jItemReader;
import org.springframework.util.Assert;
/**
* A builder for the {@link Neo4jItemReader}.
*
* @author Glenn Renfro
* @see Neo4jItemReader
*/
public class 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;
private boolean saveState = true;
private String name;
private int maxItemCount = Integer.MAX_VALUE;
private int currentItemCount;
/**
* Configure if the state of the {@link org.springframework.batch.item.ItemStreamSupport}
* should be persisted within the {@link org.springframework.batch.item.ExecutionContext}
* for restart purposes.
*
* @param saveState defaults to true
* @return The current instance of the builder.
*/
public Neo4jItemReaderBuilder<T> saveState(boolean saveState) {
this.saveState = saveState;
return this;
}
/**
* The name used to calculate the key within the
* {@link org.springframework.batch.item.ExecutionContext}. Required if
* {@link #saveState(boolean)} is set to true.
*
* @param name name of the reader instance
* @return The current instance of the builder.
* @see org.springframework.batch.item.ItemStreamSupport#setName(String)
*/
public Neo4jItemReaderBuilder<T> name(String name) {
this.name = name;
return this;
}
/**
* Configure the max number of items to be read.
*
* @param maxItemCount the max items to be read
* @return The current instance of the builder.
* @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setMaxItemCount(int)
*/
public Neo4jItemReaderBuilder<T> maxItemCount(int maxItemCount) {
this.maxItemCount = maxItemCount;
return this;
}
/**
* Index for the current item. Used on restarts to indicate where to start from.
*
* @param currentItemCount current index
* @return this instance for method chaining
* @see org.springframework.batch.item.support.AbstractItemCountingItemStreamItemReader#setCurrentItemCount(int)
*/
public Neo4jItemReaderBuilder<T> currentItemCount(int currentItemCount) {
this.currentItemCount = currentItemCount;
return this;
}
/**
* 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

@@ -0,0 +1,76 @@
/*
* Copyright 2017-2021 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
*
* https://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.extensions.neo4j.builder;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.batch.item.data.Neo4jItemWriter;
import org.springframework.util.Assert;
/**
* A builder implementation for the {@link Neo4jItemWriter}
*
* @author Glenn Renfro
* @see Neo4jItemWriter
*/
public class Neo4jItemWriterBuilder<T> {
private boolean delete = false;
private SessionFactory sessionFactory;
/**
* Boolean flag indicating whether the writer should save or delete the item at write
* time.
* @param delete true if write should delete item, false if item should be saved.
* Default is false.
* @return The current instance of the builder
* @see Neo4jItemWriter#setDelete(boolean)
*/
public Neo4jItemWriterBuilder<T> delete(boolean delete) {
this.delete = delete;
return this;
}
/**
* Establish the session factory that will be used to create {@link Session} instances
* for interacting with Neo4j.
* @param sessionFactory sessionFactory to be used.
* @return The current instance of the builder
* @see Neo4jItemWriter#setSessionFactory(SessionFactory)
*/
public Neo4jItemWriterBuilder<T> sessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
return this;
}
/**
* Validates and builds a {@link org.springframework.batch.item.data.Neo4jItemWriter}.
*
* @return a {@link Neo4jItemWriter}
*/
public Neo4jItemWriter<T> build() {
Assert.notNull(sessionFactory, "sessionFactory is required.");
Neo4jItemWriter<T> writer = new Neo4jItemWriter<>();
writer.setDelete(this.delete);
writer.setSessionFactory(this.sessionFactory);
return writer;
}
}

View File

@@ -0,0 +1,202 @@
/*
* Copyright 2013-2021 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
*
* https://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.extensions.neo4j;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
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.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.when;
public class Neo4jItemReaderTests {
@Rule
public MockitoRule rule = MockitoJUnit.rule().silent();
@Mock
private Iterable<String> result;
@Mock
private SessionFactory sessionFactory;
@Mock
private Session session;
private Neo4jItemReader<String> buildSessionBasedReader() throws Exception {
Neo4jItemReader<String> reader = new Neo4jItemReader<>();
reader.setSessionFactory(this.sessionFactory);
reader.setTargetType(String.class);
reader.setStartStatement("n=node(*)");
reader.setReturnStatement("*");
reader.setOrderByStatement("n.age");
reader.setPageSize(50);
reader.afterPropertiesSet();
return reader;
}
@Test
public void testAfterPropertiesSet() throws Exception {
Neo4jItemReader<String> reader = new Neo4jItemReader<>();
try {
reader.afterPropertiesSet();
fail("SessionFactory was not set but exception was not thrown.");
} catch (IllegalStateException iae) {
assertEquals("A SessionFactory is required", iae.getMessage());
} catch (Throwable t) {
fail("Wrong exception was thrown:" + t);
}
reader.setSessionFactory(this.sessionFactory);
try {
reader.afterPropertiesSet();
fail("Target 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();
reader = new Neo4jItemReader<>();
reader.setSessionFactory(this.sessionFactory);
reader.setTargetType(String.class);
reader.setStartStatement("n=node(*)");
reader.setReturnStatement("n.name, n.phone");
reader.setOrderByStatement("n.age");
reader.afterPropertiesSet();
}
@SuppressWarnings("unchecked")
@Test
public void testNullResultsWithSession() throws Exception {
Neo4jItemReader<String> itemReader = buildSessionBasedReader();
ArgumentCaptor<String> query = ArgumentCaptor.forClass(String.class);
when(this.sessionFactory.openSession()).thenReturn(this.session);
when(this.session.query(eq(String.class), query.capture(), isNull())).thenReturn(null);
assertFalse(itemReader.doPageRead().hasNext());
assertEquals("START n=node(*) RETURN * ORDER BY n.age SKIP 0 LIMIT 50", query.getValue());
}
@SuppressWarnings("unchecked")
@Test
public void testNoResultsWithSession() throws Exception {
Neo4jItemReader<String> itemReader = buildSessionBasedReader();
ArgumentCaptor<String> query = ArgumentCaptor.forClass(String.class);
when(this.sessionFactory.openSession()).thenReturn(this.session);
when(this.session.query(eq(String.class), query.capture(), isNull())).thenReturn(result);
when(result.iterator()).thenReturn(Collections.emptyIterator());
assertFalse(itemReader.doPageRead().hasNext());
assertEquals("START n=node(*) RETURN * ORDER BY n.age SKIP 0 LIMIT 50", query.getValue());
}
@SuppressWarnings("serial")
@Test
public void testResultsWithMatchAndWhereWithSession() throws Exception {
Neo4jItemReader<String> itemReader = buildSessionBasedReader();
itemReader.setMatchStatement("n -- m");
itemReader.setWhereStatement("has(n.name)");
itemReader.setReturnStatement("m");
itemReader.afterPropertiesSet();
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());
assertTrue(itemReader.doPageRead().hasNext());
}
@SuppressWarnings("serial")
@Test
public void testResultsWithMatchAndWhereWithParametersWithSession() throws Exception {
Neo4jItemReader<String> itemReader = buildSessionBasedReader();
Map<String, Object> params = new HashMap<>();
params.put("foo", "bar");
itemReader.setParameterValues(params);
itemReader.setMatchStatement("n -- m");
itemReader.setWhereStatement("has(n.name)");
itemReader.setReturnStatement("m");
itemReader.afterPropertiesSet();
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());
assertTrue(itemReader.doPageRead().hasNext());
}
}

View File

@@ -0,0 +1,149 @@
/*
* Copyright 2013-2021 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
*
* https://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.extensions.neo4j;
import java.util.ArrayList;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
public class Neo4jItemWriterTests {
@Rule
public MockitoRule rule = MockitoJUnit.rule().silent();
private Neo4jItemWriter<String> writer;
@Mock
private SessionFactory sessionFactory;
@Mock
private Session session;
@Test
public void testAfterPropertiesSet() throws Exception{
writer = new Neo4jItemWriter<>();
try {
writer.afterPropertiesSet();
fail("SessionFactory was not set but exception was not thrown.");
} catch (IllegalStateException iae) {
assertEquals("A SessionFactory is required", iae.getMessage());
} catch (Throwable t) {
fail("Wrong exception was thrown.");
}
writer.setSessionFactory(this.sessionFactory);
writer.afterPropertiesSet();
writer = new Neo4jItemWriter<>();
writer.setSessionFactory(this.sessionFactory);
writer.afterPropertiesSet();
}
@Test
public void testWriteNullSession() throws Exception {
writer = new Neo4jItemWriter<>();
writer.setSessionFactory(this.sessionFactory);
writer.afterPropertiesSet();
writer.write(null);
verifyNoInteractions(this.session);
}
@Test
public void testWriteNullWithSession() throws Exception {
writer = new Neo4jItemWriter<>();
writer.setSessionFactory(this.sessionFactory);
writer.afterPropertiesSet();
when(this.sessionFactory.openSession()).thenReturn(this.session);
writer.write(null);
verifyNoInteractions(this.session);
}
@Test
public void testWriteNoItemsWithSession() throws Exception {
writer = new Neo4jItemWriter<>();
writer.setSessionFactory(this.sessionFactory);
writer.afterPropertiesSet();
when(this.sessionFactory.openSession()).thenReturn(this.session);
writer.write(new ArrayList<>());
verifyNoInteractions(this.session);
}
@Test
public void testWriteItemsWithSession() throws Exception {
writer = new Neo4jItemWriter<>();
writer.setSessionFactory(this.sessionFactory);
writer.afterPropertiesSet();
List<String> items = new ArrayList<>();
items.add("foo");
items.add("bar");
when(this.sessionFactory.openSession()).thenReturn(this.session);
writer.write(items);
verify(this.session).save("foo");
verify(this.session).save("bar");
}
@Test
public void testDeleteItemsWithSession() throws Exception {
writer = new Neo4jItemWriter<>();
writer.setSessionFactory(this.sessionFactory);
writer.afterPropertiesSet();
List<String> items = new ArrayList<>();
items.add("foo");
items.add("bar");
writer.setDelete(true);
when(this.sessionFactory.openSession()).thenReturn(this.session);
writer.write(items);
verify(this.session).delete("foo");
verify(this.session).delete("bar");
}
}

View File

@@ -0,0 +1,290 @@
/*
* Copyright 2017-2021 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
*
* https://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.extensions.neo4j.builder;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
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 {
@Rule
public MockitoRule rule = MockitoJUnit.rule().silent();
@Mock
private Iterable<String> result;
@Mock
private SessionFactory sessionFactory;
@Mock
private Session session;
@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());
}
}
}

View File

@@ -0,0 +1,95 @@
/*
* Copyright 2017-2021 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
*
* https://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.extensions.neo4j.builder;
import java.util.ArrayList;
import java.util.List;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.neo4j.ogm.session.Session;
import org.neo4j.ogm.session.SessionFactory;
import org.springframework.batch.item.data.Neo4jItemWriter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* @author Glenn Renfro
*/
public class Neo4jItemWriterBuilderTests {
@Rule
public MockitoRule rule = MockitoJUnit.rule().silent();
@Mock
private SessionFactory sessionFactory;
@Mock
private Session session;
@Test
public void testBasicWriter() throws Exception{
Neo4jItemWriter<String> writer = new Neo4jItemWriterBuilder<String>()
.sessionFactory(this.sessionFactory)
.build();
List<String> items = new ArrayList<>();
items.add("foo");
items.add("bar");
when(this.sessionFactory.openSession()).thenReturn(this.session);
writer.write(items);
verify(this.session).save("foo");
verify(this.session).save("bar");
verify(this.session, never()).delete("foo");
verify(this.session, never()).delete("bar");
}
@Test
public void testBasicDelete() throws Exception{
Neo4jItemWriter<String> writer = new Neo4jItemWriterBuilder<String>().delete(true).sessionFactory(this.sessionFactory).build();
List<String> items = new ArrayList<>();
items.add("foo");
items.add("bar");
when(this.sessionFactory.openSession()).thenReturn(this.session);
writer.write(items);
verify(this.session).delete("foo");
verify(this.session).delete("bar");
verify(this.session, never()).save("foo");
verify(this.session, never()).save("bar");
}
@Test
public void testNoSessionFactory() {
try {
new Neo4jItemWriterBuilder<String>().build();
fail("SessionFactory was not set but exception was not thrown.");
} catch (IllegalArgumentException iae) {
assertEquals("sessionFactory is required.", iae.getMessage());
}
}
}