diff --git a/.github/workflows/spring-batch-geode.yml b/.github/workflows/spring-batch-geode.yml
new file mode 100644
index 0000000..aff5974
--- /dev/null
+++ b/.github/workflows/spring-batch-geode.yml
@@ -0,0 +1,21 @@
+name: Spring Batch Geode
+
+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-geode
diff --git a/README.md b/README.md
index e70bde6..a72faa0 100644
--- a/README.md
+++ b/README.md
@@ -6,12 +6,13 @@ This project is part of the [Spring organization][] on GitHub.
## Available Modules
-| Module | Description | Lead | Version | CI build |
-| -------|-------------| -----| ------- |----------|
-| spring-batch-excel | Support for [Microsoft Excel] | [@mdeinum](https://github.com/mdeinum) | 0.1.1 | [](https://github.com/spring-projects/spring-batch-extensions/actions/workflows/spring-batch-excel.yml) |
-| spring-batch-elasticsearch | Support for [Elasticsearch] | [@parikshitdutta](https://github.com/parikshitdutta) | 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 | [](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 | [](https://github.com/spring-projects/spring-batch-extensions/actions/workflows/spring-batch-neo4j.yml) |
+| Module | Description | Lead | Version | CI build |
+|----------------------------|-------------------------------|------------------------------------------------------| ------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| spring-batch-excel | Support for [Microsoft Excel] | [@mdeinum](https://github.com/mdeinum) | 0.1.1 | [](https://github.com/spring-projects/spring-batch-extensions/actions/workflows/spring-batch-excel.yml) |
+| spring-batch-elasticsearch | Support for [Elasticsearch] | [@parikshitdutta](https://github.com/parikshitdutta) | 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 | [](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 | [](https://github.com/spring-projects/spring-batch-extensions/actions/workflows/spring-batch-neo4j.yml) |
+| spring-batch-geode | Support for [Apache Geode] | TBA | 0.1.0-SNAPSHOT | [](https://github.com/spring-projects/spring-batch-extensions/actions/workflows/spring-batch-geode.yml) |
## Getting support
@@ -105,6 +106,7 @@ noted differently for individual extension Modules, but this should be the rare
[Elasticsearch]: https://www.elastic.co
[Google BigQuery]: https://cloud.google.com/bigquery
[Neo4j]: https://neo4j.com
+[Apache Geode]: https://geode.apache.org
[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
diff --git a/spring-batch-geode/.mvn/wrapper/maven-wrapper.jar b/spring-batch-geode/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..c6feb8b
Binary files /dev/null and b/spring-batch-geode/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/spring-batch-geode/.mvn/wrapper/maven-wrapper.properties b/spring-batch-geode/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..be4fea7
--- /dev/null
+++ b/spring-batch-geode/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1 @@
+distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
diff --git a/spring-batch-geode/README.adoc b/spring-batch-geode/README.adoc
new file mode 100644
index 0000000..b179ad3
--- /dev/null
+++ b/spring-batch-geode/README.adoc
@@ -0,0 +1,3 @@
+# spring-batch-geode
+
+Spring Batch extension for Apache Geode.
diff --git a/spring-batch-geode/mvnw b/spring-batch-geode/mvnw
new file mode 100755
index 0000000..f13e138
--- /dev/null
+++ b/spring-batch-geode/mvnw
@@ -0,0 +1,236 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Maven2 Start Up Batch script
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# M2_HOME - location of maven2's installed home dir
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "`uname`" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ #
+ # Look for the Apple JDKs first to preserve the existing behaviour, and then look
+ # for the new JDKs provided by Oracle.
+ #
+ if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
+ #
+ # Oracle JDKs
+ #
+ export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
+ fi
+
+ if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
+ #
+ # Apple JDKs
+ #
+ export JAVA_HOME=`/usr/libexec/java_home`
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=`java-config --jre-home`
+ fi
+fi
+
+if [ -z "$M2_HOME" ] ; then
+ ## resolve links - $0 may be a link to maven's home
+ PRG="$0"
+
+ # need this for relative symlinks
+ while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG="`dirname "$PRG"`/$link"
+ fi
+ done
+
+ saveddir=`pwd`
+
+ M2_HOME=`dirname "$PRG"`/..
+
+ # make it fully qualified
+ M2_HOME=`cd "$M2_HOME" && pwd`
+
+ cd "$saveddir"
+ # echo Using m2 at $M2_HOME
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --unix "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+fi
+
+# For Migwn, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME="`(cd "$M2_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ # TODO classpath?
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="`which javac`"
+ if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=`which readlink`
+ if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ if $darwin ; then
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ else
+ javaExecutable="`readlink -f \"$javaExecutable\"`"
+ fi
+ javaHome="`dirname \"$javaExecutable\"`"
+ javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="`which java`"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ local basedir=$(pwd)
+ local wdir=$(pwd)
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ wdir=$(cd "$wdir/.."; pwd)
+ done
+ echo "${basedir}"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ echo "$(tr -s '\n' ' ' < "$1")"
+ fi
+}
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$M2_HOME" ] &&
+ M2_HOME=`cygpath --path --windows "$M2_HOME"`
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in $@
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spring-batch-geode/mvnw.cmd b/spring-batch-geode/mvnw.cmd
new file mode 100644
index 0000000..bb9bb46
--- /dev/null
+++ b/spring-batch-geode/mvnw.cmd
@@ -0,0 +1,146 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Maven2 Start Up Batch script
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM M2_HOME - location of maven2's installed home dir
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
+if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %*
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+
+set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar""
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in %*
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
+if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%" == "on" pause
+
+if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
+
+exit /B %ERROR_CODE%
diff --git a/spring-batch-geode/pom.xml b/spring-batch-geode/pom.xml
new file mode 100644
index 0000000..405e295
--- /dev/null
+++ b/spring-batch-geode/pom.xml
@@ -0,0 +1,136 @@
+
+
+
+ 4.0.0
+
+ org.springframework.batch.extensions
+ spring-batch-geode
+ 0.1.0-SNAPSHOT
+ Spring Batch Geode
+ Spring Batch extension for Apache Geode
+ https://github.com/spring-projects/spring-batch-extensions/tree/main/spring-batch-geode
+
+
+
+ Apache 2.0
+ https://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ https://github.com/spring-projects/spring-batch-extensions
+ git://github.com/spring-projects/spring-batch-extensions.git
+ git@github.com:spring-projects/spring-batch-extensions.git
+
+
+
+ UTF-8
+ UTF-8
+ 1.8
+
+
+ 4.3.7
+ 2.7.5
+
+
+ 5.8.2
+ 4.8.0
+
+
+ 3.8.1
+ 3.2.0
+ 3.2.1
+
+
+
+
+ org.springframework.batch
+ spring-batch-core
+ ${spring.batch.version}
+
+
+ org.springframework.data
+ spring-data-geode
+ ${spring-data-geode.version}
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${junit-jupiter.version}
+ test
+
+
+ org.mockito
+ mockito-core
+ ${mockito.version}
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito.version}
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ ${java.version}
+ ${java.version}
+
+ -Xlint:all,deprecation
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+ ${maven-javadoc-plugin.version}
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-source-plugin
+ ${maven-source-plugin.version}
+
+
+ attach-sources
+
+ jar
+
+
+
+
+
+
+
+
diff --git a/spring-batch-geode/src/main/java/org/springframework/batch/extensions/geode/GemfireItemWriter.java b/spring-batch-geode/src/main/java/org/springframework/batch/extensions/geode/GemfireItemWriter.java
new file mode 100644
index 0000000..b879c43
--- /dev/null
+++ b/spring-batch-geode/src/main/java/org/springframework/batch/extensions/geode/GemfireItemWriter.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2002-2022 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.geode;
+
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.item.KeyValueItemWriter;
+import org.springframework.data.gemfire.GemfireOperations;
+import org.springframework.data.gemfire.GemfireTemplate;
+import org.springframework.util.Assert;
+
+/**
+ * An {@link ItemWriter} that stores items in GemFire
+ *
+ * @author David Turanski
+ * @since 2.2
+ *
+ */
+public class GemfireItemWriter extends KeyValueItemWriter {
+
+ private GemfireOperations gemfireTemplate;
+
+ /**
+ * @param gemfireTemplate the {@link GemfireTemplate} to set
+ */
+ public void setTemplate(GemfireTemplate gemfireTemplate) {
+ this.gemfireTemplate = gemfireTemplate;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * org.springframework.batch.item.KeyValueItemWriter#writeKeyValue(java.lang.Object,
+ * java.lang.Object)
+ */
+ @Override
+ protected void writeKeyValue(K key, V value) {
+ if (delete) {
+ gemfireTemplate.remove(key);
+ }
+ else {
+ gemfireTemplate.put(key, value);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.springframework.batch.item.KeyValueItemWriter#init()
+ */
+ @Override
+ protected void init() {
+ Assert.notNull(gemfireTemplate, "A GemfireTemplate is required.");
+ }
+
+}
diff --git a/spring-batch-geode/src/main/java/org/springframework/batch/extensions/geode/SpELMappingGemfireItemWriter.java b/spring-batch-geode/src/main/java/org/springframework/batch/extensions/geode/SpELMappingGemfireItemWriter.java
new file mode 100644
index 0000000..78d2697
--- /dev/null
+++ b/spring-batch-geode/src/main/java/org/springframework/batch/extensions/geode/SpELMappingGemfireItemWriter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2002-2022 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.geode;
+
+import org.springframework.batch.item.SpELItemKeyMapper;
+import org.springframework.util.Assert;
+
+/**
+ * A convenient {@link GemfireItemWriter} implementation that uses a
+ * {@link SpELItemKeyMapper}
+ *
+ * @author David Turanski
+ * @since 2.2
+ */
+public class SpELMappingGemfireItemWriter extends GemfireItemWriter {
+
+ /**
+ * A constructor that accepts a SpEL expression used to derive the key
+ * @param keyExpression
+ */
+ SpELMappingGemfireItemWriter(String keyExpression) {
+ super();
+ Assert.hasText(keyExpression, "a valid keyExpression is required.");
+ setItemKeyMapper(new SpELItemKeyMapper<>(keyExpression));
+ }
+
+}
diff --git a/spring-batch-geode/src/main/java/org/springframework/batch/extensions/geode/builder/GemfireItemWriterBuilder.java b/spring-batch-geode/src/main/java/org/springframework/batch/extensions/geode/builder/GemfireItemWriterBuilder.java
new file mode 100644
index 0000000..b1e7f95
--- /dev/null
+++ b/spring-batch-geode/src/main/java/org/springframework/batch/extensions/geode/builder/GemfireItemWriterBuilder.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2017-2022 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.geode.builder;
+
+import org.springframework.batch.extensions.geode.GemfireItemWriter;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.data.gemfire.GemfireTemplate;
+import org.springframework.util.Assert;
+
+/**
+ * A builder implementation for the {@link GemfireItemWriter}
+ *
+ * @author Glenn Renfro
+ * @since 4.0
+ * @see GemfireItemWriterBuilder
+ */
+public class GemfireItemWriterBuilder {
+
+ private GemfireTemplate template;
+
+ private Converter itemKeyMapper;
+
+ private boolean delete;
+
+ /**
+ * Establishes the GemfireTemplate the writer should use.
+ * @param template the {@link GemfireTemplate} to set.
+ * @return The current instance of the builder.
+ * @see GemfireItemWriter#setTemplate(GemfireTemplate)
+ */
+ public GemfireItemWriterBuilder template(GemfireTemplate template) {
+ this.template = template;
+
+ return this;
+ }
+
+ /**
+ * Set the {@link Converter} to use to derive the key from the item.
+ * @param itemKeyMapper the Converter to use.
+ * @return The current instance of the builder.
+ * @see GemfireItemWriter#setItemKeyMapper(Converter)
+ */
+ public GemfireItemWriterBuilder itemKeyMapper(Converter itemKeyMapper) {
+ this.itemKeyMapper = itemKeyMapper;
+
+ return this;
+ }
+
+ /**
+ * Indicates if the items being passed to the writer are to be saved or removed from
+ * the data store. If set to false (default), the items will be saved. If set to true,
+ * the items will be removed.
+ * @param delete removal indicator.
+ * @return The current instance of the builder.
+ * @see GemfireItemWriter#setDelete(boolean)
+ */
+ public GemfireItemWriterBuilder delete(boolean delete) {
+ this.delete = delete;
+
+ return this;
+ }
+
+ /**
+ * Validates and builds a {@link GemfireItemWriter}.
+ * @return a {@link GemfireItemWriter}
+ */
+ public GemfireItemWriter build() {
+ Assert.notNull(this.template, "template is required.");
+ Assert.notNull(this.itemKeyMapper, "itemKeyMapper is required.");
+
+ GemfireItemWriter writer = new GemfireItemWriter<>();
+ writer.setTemplate(this.template);
+ writer.setItemKeyMapper(this.itemKeyMapper);
+ writer.setDelete(this.delete);
+ return writer;
+ }
+
+}
diff --git a/spring-batch-geode/src/test/java/org/springframework/batch/extensions/geode/GemfireItemWriterTests.java b/spring-batch-geode/src/test/java/org/springframework/batch/extensions/geode/GemfireItemWriterTests.java
new file mode 100644
index 0000000..a8ac8b9
--- /dev/null
+++ b/spring-batch-geode/src/test/java/org/springframework/batch/extensions/geode/GemfireItemWriterTests.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright 2013-2022 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.geode;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.springframework.batch.item.SpELItemKeyMapper;
+import org.springframework.data.gemfire.GemfireTemplate;
+
+@ExtendWith(MockitoExtension.class)
+class GemfireItemWriterTests {
+
+ private GemfireItemWriter writer;
+
+ @Mock
+ private GemfireTemplate template;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ writer = new GemfireItemWriter<>();
+ writer.setTemplate(template);
+ writer.setItemKeyMapper(new SpELItemKeyMapper<>("bar.val"));
+ writer.afterPropertiesSet();
+ }
+
+ @Test
+ void testAfterPropertiesSet() throws Exception {
+ writer = new GemfireItemWriter<>();
+ assertThrows(IllegalArgumentException.class, writer::afterPropertiesSet);
+
+ writer.setTemplate(template);
+ assertThrows(IllegalArgumentException.class, writer::afterPropertiesSet);
+
+ writer.setItemKeyMapper(new SpELItemKeyMapper<>("foo"));
+ writer.afterPropertiesSet();
+ }
+
+ @Test
+ void testBasicWrite() throws Exception {
+ List chunk = new ArrayList<>();
+ chunk.add(new Foo(new Bar("val1")));
+ chunk.add(new Foo(new Bar("val2")));
+ writer.write(chunk);
+
+ verify(template).put("val1", chunk.get(0));
+ verify(template).put("val2", chunk.get(1));
+ }
+
+ @Test
+ void testBasicDelete() throws Exception {
+ List chunk = new ArrayList<>();
+ chunk.add(new Foo(new Bar("val1")));
+ chunk.add(new Foo(new Bar("val2")));
+ writer.setDelete(true);
+ writer.write(chunk);
+
+ verify(template).remove("val1");
+ verify(template).remove("val2");
+ }
+
+ @Test
+ void testWriteWithCustomItemKeyMapper() throws Exception {
+ List chunk = new ArrayList<>();
+ chunk.add(new Foo(new Bar("val1")));
+ chunk.add(new Foo(new Bar("val2")));
+ writer = new GemfireItemWriter<>();
+ writer.setTemplate(template);
+ writer.setItemKeyMapper(item -> {
+ String index = item.bar.val.replaceAll("val", "");
+ return "item" + index;
+ });
+ writer.afterPropertiesSet();
+ writer.write(chunk);
+
+ verify(template).put("item1", chunk.get(0));
+ verify(template).put("item2", chunk.get(1));
+ }
+
+ @Test
+ void testWriteNoTransactionNoItems() throws Exception {
+ writer.write(null);
+ verifyNoInteractions(template);
+ }
+
+ static class Foo {
+
+ public Bar bar;
+
+ public Foo(Bar bar) {
+ this.bar = bar;
+ }
+
+ }
+
+ static class Bar {
+
+ public String val;
+
+ public Bar(String b1) {
+ this.val = b1;
+ }
+
+ }
+
+}
diff --git a/spring-batch-geode/src/test/java/org/springframework/batch/extensions/geode/builder/GemfireItemWriterBuilderTests.java b/spring-batch-geode/src/test/java/org/springframework/batch/extensions/geode/builder/GemfireItemWriterBuilderTests.java
new file mode 100644
index 0000000..27df278
--- /dev/null
+++ b/spring-batch-geode/src/test/java/org/springframework/batch/extensions/geode/builder/GemfireItemWriterBuilderTests.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017-2022 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.geode.builder;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import org.springframework.batch.item.SpELItemKeyMapper;
+import org.springframework.batch.extensions.geode.GemfireItemWriter;
+import org.springframework.data.gemfire.GemfireTemplate;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+/**
+ * @author Glenn Renfro
+ * @author Mahmoud Ben Hassine
+ */
+@ExtendWith(MockitoExtension.class)
+class GemfireItemWriterBuilderTests {
+
+ @Mock
+ private GemfireTemplate template;
+
+ private SpELItemKeyMapper itemKeyMapper;
+
+ private List items;
+
+ @BeforeEach
+ void setUp() {
+ this.items = new ArrayList<>();
+ this.items.add(new GemfireItemWriterBuilderTests.Foo(new GemfireItemWriterBuilderTests.Bar("val1")));
+ this.items.add(new GemfireItemWriterBuilderTests.Foo(new GemfireItemWriterBuilderTests.Bar("val2")));
+ this.itemKeyMapper = new SpELItemKeyMapper<>("bar.val");
+ }
+
+ @Test
+ void testBasicWrite() throws Exception {
+ GemfireItemWriter writer = new GemfireItemWriterBuilder()
+ .template(this.template).itemKeyMapper(this.itemKeyMapper).build();
+
+ writer.write(this.items);
+
+ verify(this.template).put("val1", items.get(0));
+ verify(this.template).put("val2", items.get(1));
+ verify(this.template, never()).remove("val1");
+ verify(this.template, never()).remove("val2");
+ }
+
+ @Test
+ void testBasicDelete() throws Exception {
+ GemfireItemWriter writer = new GemfireItemWriterBuilder()
+ .template(this.template).delete(true).itemKeyMapper(this.itemKeyMapper).build();
+
+ writer.write(this.items);
+
+ verify(this.template).remove("val1");
+ verify(this.template).remove("val2");
+ verify(this.template, never()).put("val1", items.get(0));
+ verify(this.template, never()).put("val2", items.get(1));
+ }
+
+ @Test
+ void testNullTemplate() {
+ try {
+ new GemfireItemWriterBuilder()
+ .itemKeyMapper(this.itemKeyMapper);
+ } catch (Exception exception) {
+ assertEquals("template is required.", exception.getMessage());
+ }
+ }
+
+ @Test
+ void testNullItemKeyMapper() {
+ try {
+ new GemfireItemWriterBuilder().template(this.template);
+ } catch (Exception exception) {
+ assertEquals("template is required.", exception.getMessage());
+ }
+ }
+
+ static class Foo {
+
+ public GemfireItemWriterBuilderTests.Bar bar;
+
+ public Foo(GemfireItemWriterBuilderTests.Bar bar) {
+ this.bar = bar;
+ }
+
+ }
+
+ static class Bar {
+
+ public String val;
+
+ public Bar(String b1) {
+ this.val = b1;
+ }
+
+ }
+
+}