Add module for Neo4j
This initial import is a copy of the code from Spring Batch v4.3.3.
This commit is contained in:
21
.github/workflows/spring-batch-neo4j.yml
vendored
Normal file
21
.github/workflows/spring-batch-neo4j.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Spring Batch Neo4j
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
- name: Build with Maven
|
||||
run: mvn -B package --file pom.xml
|
||||
working-directory: spring-batch-neo4j
|
||||
@@ -11,6 +11,7 @@ This project is part of the [Spring organization][] on GitHub.
|
||||
| spring-batch-excel | Support for [Microsoft Excel] | [@mdeinum](https://github.com/mdeinum) | 0.1.0 | [](https://github.com/spring-projects/spring-batch-extensions/actions/workflows/spring-batch-excel.yml) |
|
||||
| spring-batch-elasticsearch | Support for [Elasticsearch] | TBA | 0.1.0-SNAPSHOT | [](https://github.com/spring-projects/spring-batch-extensions/actions/workflows/spring-batch-elasticsearch.yml) |
|
||||
| spring-batch-bigquery | Support for [Google BigQuery] | [@dgray16](https://github.com/dgray16) | 0.1.0-SNAPSHOT | [](https://github.com/spring-projects/spring-batch-extensions/actions/workflows/spring-batch-bigquery.yml) |
|
||||
| spring-batch-neo4j | Support for [Neo4j] | [@michael-simons](https://github.com/michael-simons) | 0.1.0-SNAPSHOT | [](https://github.com/spring-projects/spring-batch-extensions/actions/workflows/spring-batch-neo4j.yml) |
|
||||
|
||||
## Getting support
|
||||
|
||||
@@ -87,6 +88,8 @@ Follow the Spring Batch team members and contributors on Twitter:
|
||||
* [@michaelminella](https://twitter.com/michaelminella) - Michael Minella
|
||||
* [@b_e_n_a_s](https://twitter.com/b_e_n_a_s) - Mahmoud Ben Hassine
|
||||
* [@mdeinum](https://twitter.com/mdeinum) - Marten Deinum
|
||||
* [@rotnroll666](https://twitter.com/rotnroll666) - Michael Simons
|
||||
* [@meistermeier](https://twitter.com/meistermeier) - Gerrit Meier
|
||||
|
||||
## License
|
||||
|
||||
@@ -100,6 +103,7 @@ noted differently for individual extension Modules, but this should be the rare
|
||||
[Microsoft Excel]: https://www.microsoft.com/en-us/microsoft-365/excel
|
||||
[Elasticsearch]: https://www.elastic.co
|
||||
[Google BigQuery]: https://cloud.google.com/bigquery
|
||||
[Neo4j]: https://neo4j.com
|
||||
[spring-batch tag]: https://stackoverflow.com/questions/tagged/spring-batch
|
||||
[Spring Batch]: https://github.com/spring-projects/spring-batch
|
||||
[Spring Boot]: https://github.com/spring-projects/spring-boot
|
||||
|
||||
31
spring-batch-neo4j/README.md
Normal file
31
spring-batch-neo4j/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# spring-batch-neo4j
|
||||
|
||||
This extension contains an `ItemReader` and `ItemWriter` implementations for [Neo4j](https://neo4j.com).
|
||||
|
||||
# Usage example
|
||||
|
||||
The `Neo4jItemReader` can be configured as follows:
|
||||
|
||||
```java
|
||||
SessionFactory sessionFactory = ...
|
||||
Neo4jItemReader<String> itemReader = new Neo4jItemReaderBuilder<String>()
|
||||
.sessionFactory(sessionFactory)
|
||||
.name("itemReader")
|
||||
.targetType(String.class)
|
||||
.startStatement("n=node(*)")
|
||||
.orderByStatement("n.age")
|
||||
.matchStatement("n -- m")
|
||||
.whereStatement("has(n.name)")
|
||||
.returnStatement("m")
|
||||
.pageSize(50)
|
||||
.build();
|
||||
```
|
||||
|
||||
The `Neo4jItemWriter` can be configured as follows:
|
||||
|
||||
```java
|
||||
SessionFactory sessionFactory = ...
|
||||
Neo4jItemWriter<String> writer = new Neo4jItemWriterBuilder<String>()
|
||||
.sessionFactory(sessionFactory)
|
||||
.build();
|
||||
```
|
||||
157
spring-batch-neo4j/pom.xml
Normal file
157
spring-batch-neo4j/pom.xml
Normal file
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 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.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework.batch.extensions</groupId>
|
||||
<artifactId>spring-batch-neo4j</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
<name>Spring Batch Neo4j</name>
|
||||
<description>Spring Batch extension for Neo4j</description>
|
||||
<url>https://github.com/spring-projects/spring-batch-extensions/tree/main/spring-batch-neo4j</url>
|
||||
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache 2.0</name>
|
||||
<url>https://www.apache.org/licenses/LICENSE-2.0.txt</url>
|
||||
<distribution>repo</distribution>
|
||||
</license>
|
||||
</licenses>
|
||||
|
||||
<scm>
|
||||
<url>https://github.com/spring-projects/spring-batch-extensions</url>
|
||||
<connection>git://github.com/spring-projects/spring-batch-extensions.git</connection>
|
||||
<developerConnection>git@github.com:spring-projects/spring-batch-extensions.git</developerConnection>
|
||||
</scm>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>michael-simons</id>
|
||||
<name>Michael Simons</name>
|
||||
<url>https://github.com/michael-simons</url>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>meistermeier</id>
|
||||
<name>Gerrit Meier</name>
|
||||
<url>https://github.com/meistermeier</url>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
|
||||
<!-- Production dependencies-->
|
||||
<spring.batch.version>4.3.3</spring.batch.version>
|
||||
<neo4j-ogm-core.version>3.2.21</neo4j-ogm-core.version>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<assertj.version>3.18.1</assertj.version>
|
||||
<junit.version>4.13.2</junit.version>
|
||||
<mockito.version>3.6.0</mockito.version>
|
||||
|
||||
<!-- Maven plugins -->
|
||||
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
|
||||
<maven-javadoc-plugin.version>3.2.0</maven-javadoc-plugin.version>
|
||||
<maven-source-plugin.version>3.2.1</maven-source-plugin.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.batch</groupId>
|
||||
<artifactId>spring-batch-core</artifactId>
|
||||
<version>${spring.batch.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.batch</groupId>
|
||||
<artifactId>spring-batch-infrastructure</artifactId>
|
||||
<version>${spring.batch.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.neo4j</groupId>
|
||||
<artifactId>neo4j-ogm-core</artifactId>
|
||||
<version>${neo4j-ogm-core.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test Dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>${mockito.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<compilerArgs>
|
||||
<arg>-Xlint:all,deprecation</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>${maven-javadoc-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>${maven-source-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user