XD-1027: Create script-based batch ItemProcessor
This commit is contained in:
15
build.gradle
15
build.gradle
@@ -89,6 +89,8 @@ allprojects {
|
||||
xercesVersion = '2.8.1'
|
||||
xmlunitVersion = '1.5'
|
||||
xstreamVersion = '1.4.7'
|
||||
jrubyVersion = '1.1.7'
|
||||
beanshellVersion = '2.0b5'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,6 +263,16 @@ project('spring-batch-core') {
|
||||
|
||||
project('spring-batch-infrastructure') {
|
||||
description = 'Spring Batch Infrastructure'
|
||||
test {
|
||||
// permsize settings not passed down from GRADLE_OPTS nor JAVA_OPTS
|
||||
// when running certain tests causing permgen OOM when using JDK7
|
||||
// compilation is not performed with JDK6 and permgen is removed
|
||||
// starting with JDK8..
|
||||
if (JavaVersion.current().isJava7Compatible()) {
|
||||
jvmArgs '-XX:MaxPermSize=256m'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
compile "org.springframework:spring-core:$springVersion"
|
||||
@@ -285,6 +297,9 @@ project('spring-batch-infrastructure') {
|
||||
testCompile "org.xerial:sqlite-jdbc:$sqliteVersion"
|
||||
|
||||
testRuntime "com.sun.mail:javax.mail:$javaMailVersion"
|
||||
testRuntime "org.codehaus.groovy:groovy-jsr223:$groovyVersion"
|
||||
testRuntime "com.sun.script.jruby:jruby-engine:$jrubyVersion"
|
||||
testRuntime "org.beanshell:bsh:$beanshellVersion"
|
||||
|
||||
optional "javax.jms:jms-api:1.1-rev-1"
|
||||
optional "org.slf4j:slf4j-log4j12:$slf4jVersion"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.core.step.item;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.batch.core.Job;
|
||||
import org.springframework.batch.core.JobParameters;
|
||||
import org.springframework.batch.core.launch.JobLauncher;
|
||||
import org.springframework.batch.item.ItemWriter;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test job utilizing a {@link org.springframework.batch.item.support.ScriptItemProcessor}.
|
||||
* </p>
|
||||
*
|
||||
* @author Chris Schaefer
|
||||
* @since 3.1
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
public class ScriptItemProcessorTest {
|
||||
@Autowired
|
||||
private Job job;
|
||||
|
||||
@Autowired
|
||||
private JobLauncher jobLauncher;
|
||||
|
||||
@Test
|
||||
public void testScriptProcessorJob() throws Exception {
|
||||
jobLauncher.run(job, new JobParameters());
|
||||
}
|
||||
|
||||
public static class TestItemWriter implements ItemWriter<String> {
|
||||
@Override
|
||||
public void write(List<? extends String> items) throws Exception {
|
||||
Assert.notNull(items, "Items cannot be null");
|
||||
Assert.isTrue(!items.isEmpty(), "Items cannot be empty");
|
||||
Assert.isTrue(items.size() == 1, "Items should only contain one entry");
|
||||
|
||||
String item = items.get(0);
|
||||
Assert.isTrue("BLAH".equals(item), "Transformed item to write should have been: BLAH but got: " + item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans xmlns="http://www.springframework.org/schema/batch"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/batch
|
||||
http://www.springframework.org/schema/batch/spring-batch.xsd
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
|
||||
|
||||
<job id="scriptProcessorJob">
|
||||
<step id="step1">
|
||||
<tasklet>
|
||||
<chunk reader="reader" processor="processor" writer="writer" commit-interval="1"/>
|
||||
</tasklet>
|
||||
</step>
|
||||
</job>
|
||||
|
||||
<beans:bean id="transactionManager"
|
||||
class="org.springframework.batch.support.transaction.ResourcelessTransactionManager"/>
|
||||
|
||||
<beans:bean id="jobRepository" class="org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean"
|
||||
p:transactionManager-ref="transactionManager"/>
|
||||
|
||||
<beans:bean id="jobLauncher" class="org.springframework.batch.core.launch.support.SimpleJobLauncher"
|
||||
p:jobRepository-ref="jobRepository"/>
|
||||
|
||||
<beans:bean id="reader" class="org.springframework.batch.item.support.ListItemReader">
|
||||
<beans:constructor-arg>
|
||||
<beans:list>
|
||||
<beans:value>blah</beans:value>
|
||||
</beans:list>
|
||||
</beans:constructor-arg>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="processor" class="org.springframework.batch.item.support.ScriptItemProcessor"
|
||||
p:script="org/springframework/batch/core/step/item/processor-test-simple.js"/>
|
||||
|
||||
<beans:bean id="writer" class="org.springframework.batch.core.step.item.ScriptItemProcessorTest$TestItemWriter"/>
|
||||
</beans:beans>
|
||||
@@ -0,0 +1 @@
|
||||
item.toUpperCase();
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.item.support;
|
||||
|
||||
import org.springframework.scripting.support.StaticScriptSource;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.batch.item.ItemProcessor;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.scripting.ScriptEvaluator;
|
||||
import org.springframework.scripting.ScriptSource;
|
||||
import org.springframework.scripting.support.ResourceScriptSource;
|
||||
import org.springframework.scripting.support.StandardScriptEvaluator;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* {@link org.springframework.batch.item.ItemProcessor} implementation that passes the current
|
||||
* item to process to the provided script. Exposes the current item for processing via the
|
||||
* {@link org.springframework.batch.item.support.ScriptItemProcessor#ITEM_BINDING_VARIABLE_NAME}
|
||||
* key name ("item"). A custom key name can be set by invoking:
|
||||
* {@link org.springframework.batch.item.support.ScriptItemProcessor#setItemBindingVariableName}
|
||||
* with the desired key name. The thread safety of this {@link org.springframework.batch.item.ItemProcessor}
|
||||
* depends on the implementation of the {@link org.springframework.scripting.ScriptEvaluator} used.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @author Chris Schaefer
|
||||
* @since 3.0
|
||||
*/
|
||||
public class ScriptItemProcessor<I, O> implements ItemProcessor<I, O>, InitializingBean {
|
||||
private static final String ITEM_BINDING_VARIABLE_NAME = "item";
|
||||
|
||||
private String language;
|
||||
private ScriptSource script;
|
||||
private ScriptSource scriptSource;
|
||||
private ScriptEvaluator scriptEvaluator;
|
||||
private String itemBindingVariableName = ITEM_BINDING_VARIABLE_NAME;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public O process(I item) throws Exception {
|
||||
Map<String, Object> arguments = new HashMap<String, Object>();
|
||||
arguments.put(itemBindingVariableName, item);
|
||||
|
||||
return (O) scriptEvaluator.evaluate(getScriptSource(), arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the {@link org.springframework.core.io.Resource} location of the script to use.
|
||||
* The script language will be deduced from the filename extension.
|
||||
* </p>
|
||||
*
|
||||
* @param resource the {@link org.springframework.core.io.Resource} location of the script to use.
|
||||
*/
|
||||
public void setScript(Resource resource) {
|
||||
Assert.notNull(resource, "The script resource cannot be null");
|
||||
|
||||
this.script = new ResourceScriptSource(resource);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Sets the provided {@link String} as the script source code to use.
|
||||
* </p>
|
||||
*
|
||||
* @param scriptSource the {@link String} form of the script source code to use.
|
||||
* @param language the language of the script as returned by the {@link javax.script.ScriptEngineFactory}
|
||||
*/
|
||||
public void setScriptSource(String scriptSource, String language) {
|
||||
Assert.hasText(language, "Language must contain the script language");
|
||||
Assert.hasText(scriptSource, "Script source must contain the script source to evaluate");
|
||||
|
||||
this.language = language;
|
||||
this.scriptSource = new StaticScriptSource(scriptSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides the ability to change the key name that scripts use to obtain the current
|
||||
* item to process if the variable represented by:
|
||||
* {@link org.springframework.batch.item.support.ScriptItemProcessor#ITEM_BINDING_VARIABLE_NAME}
|
||||
* is not suitable ("item").
|
||||
* </p>
|
||||
*
|
||||
* @param itemBindingVariableName the desired binding variable name
|
||||
*/
|
||||
public void setItemBindingVariableName(String itemBindingVariableName) {
|
||||
this.itemBindingVariableName = itemBindingVariableName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
scriptEvaluator = new StandardScriptEvaluator();
|
||||
|
||||
Assert.state(scriptSource != null || script != null,
|
||||
"Either the script source or script file must be provided");
|
||||
|
||||
Assert.state(scriptSource == null || script == null,
|
||||
"Either a script source or script file must be provided, not both");
|
||||
|
||||
if (scriptSource != null) {
|
||||
Assert.isTrue(!StringUtils.isEmpty(language),
|
||||
"Language must be provided when using script source");
|
||||
|
||||
((StandardScriptEvaluator) scriptEvaluator).setLanguage(language);
|
||||
}
|
||||
}
|
||||
|
||||
private ScriptSource getScriptSource() {
|
||||
if (script != null) {
|
||||
return script;
|
||||
}
|
||||
|
||||
if (scriptSource != null) {
|
||||
return scriptSource;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Either a script source or script needs to be provided.");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2014 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.batch.item.support;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
|
||||
import javax.script.ScriptEngineFactory;
|
||||
import javax.script.ScriptEngineManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assume.assumeTrue;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Test cases around {@link org.springframework.batch.item.support.ScriptItemProcessor}.
|
||||
* </p>
|
||||
*
|
||||
* @author Chris Schaefer
|
||||
* @since 3.1
|
||||
*/
|
||||
public class ScriptItemProcessorTests {
|
||||
private static List<String> availableLanguages = new ArrayList<String>();
|
||||
|
||||
@BeforeClass
|
||||
public static void populateAvailableEngines() {
|
||||
List<ScriptEngineFactory> scriptEngineFactories = new ScriptEngineManager().getEngineFactories();
|
||||
|
||||
for (ScriptEngineFactory scriptEngineFactory : scriptEngineFactories) {
|
||||
availableLanguages.addAll(scriptEngineFactory.getNames());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptScriptSourceSimple() throws Exception {
|
||||
assumeTrue(languageExists("javascript"));
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("item.toUpperCase();", "javascript");
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptScriptSourceFunction() throws Exception {
|
||||
assumeTrue(languageExists("javascript"));
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("function process(item) { return item.toUpperCase(); } process(item);", "javascript");
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJRubyScriptSourceSimple() throws Exception {
|
||||
assumeTrue(languageExists("jruby"));
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("$item.upcase", "jruby");
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJRubyScriptSourceMethod() throws Exception {
|
||||
assumeTrue(languageExists("jruby"));
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("def process(item) $item.upcase end \n process($item)", "jruby");
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanShellScriptSourceSimple() throws Exception {
|
||||
assumeTrue(languageExists("bsh"));
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("item.toUpperCase();", "bsh");
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBeanShellScriptSourceFunction() throws Exception {
|
||||
assumeTrue(languageExists("bsh"));
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("String process(String item) { return item.toUpperCase(); } process(item);", "bsh");
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroovyScriptSourceSimple() throws Exception {
|
||||
assumeTrue(languageExists("groovy"));
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("item.toUpperCase();", "groovy");
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGroovyScriptSourceMethod() throws Exception {
|
||||
assumeTrue(languageExists("groovy"));
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("def process(item) { return item.toUpperCase() } \n process(item)", "groovy");
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testJavascriptScriptSimple() throws Exception {
|
||||
assumeTrue(languageExists("javascript"));
|
||||
|
||||
Resource resource = new ClassPathResource("org/springframework/batch/item/support/processor-test-simple.js");
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScript(resource);
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testNoScriptSet() throws Exception {
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testScriptSourceAndScriptResourceSet() throws Exception {
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("blah", "blah");
|
||||
scriptItemProcessor.setScript(new ClassPathResource("blah"));
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void testNoScriptSetWithoutInitBean() throws Exception {
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.process("blah");
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testScriptSourceWithNoLanguage() throws Exception {
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setScriptSource("function process(item) { return item.toUpperCase(); } process(item);", null);
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testItemBindingNameChange() throws Exception {
|
||||
assumeTrue(languageExists("javascript"));
|
||||
|
||||
ScriptItemProcessor<String, Object> scriptItemProcessor = new ScriptItemProcessor<String, Object>();
|
||||
scriptItemProcessor.setItemBindingVariableName("someOtherVarName");
|
||||
scriptItemProcessor.setScriptSource("function process(param) { return param.toUpperCase(); } process(someOtherVarName);", "javascript");
|
||||
scriptItemProcessor.afterPropertiesSet();
|
||||
|
||||
assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss"));
|
||||
}
|
||||
|
||||
private boolean languageExists(String engineName) {
|
||||
return availableLanguages.contains(engineName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
item.toUpperCase();
|
||||
Reference in New Issue
Block a user