diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/builder/ScriptItemProcessorBuilder.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/builder/ScriptItemProcessorBuilder.java new file mode 100644 index 000000000..d3f53bffa --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/item/support/builder/ScriptItemProcessorBuilder.java @@ -0,0 +1,125 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.batch.item.support.builder; + +import org.springframework.batch.item.support.ScriptItemProcessor; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Creates a fully qualified ScriptItemProcessor. + * + * @author Glenn Renfro + * + * @since 4.0 + */ +public class ScriptItemProcessorBuilder { + + private String language; + + private Resource scriptResource; + + private String scriptSource; + + private String itemBindingVariableName; + + /** + * Sets the {@link org.springframework.core.io.Resource} location of the script to + * use. The script language will be deduced from the filename extension. + * + * @param resource the {@link org.springframework.core.io.Resource} location of the + * script to use. + * @return this instance for method chaining + * @see ScriptItemProcessor#setScript(Resource) + * + */ + public ScriptItemProcessorBuilder scriptResource(Resource resource) { + this.scriptResource = resource; + + return this; + } + + /** + * Establishes the language of the script. + * + * @param language the language of the script. + * @return this instance for method chaining + * @see ScriptItemProcessor#setScriptSource(String, String) + */ + public ScriptItemProcessorBuilder language(String language) { + this.language = language; + + return this; + } + + /** + * Sets the provided {@link String} as the script source code to use. Language must + * not be null nor empty when using script. + * + * @param scriptSource the {@link String} form of the script source code to use. + * @return this instance for method chaining + * @see ScriptItemProcessor#setScriptSource(String, String) + */ + public ScriptItemProcessorBuilder scriptSource(String scriptSource) { + this.scriptSource = scriptSource; + + return this; + } + + /** + * Provides the ability to change the key name that scripts use to obtain the current + * item to process if the variable represented by: + * {@link ScriptItemProcessor#ITEM_BINDING_VARIABLE_NAME} + * is not suitable ("item"). + * + * @param itemBindingVariableName the desired binding variable name + * @return this instance for method chaining + * @see ScriptItemProcessor#setItemBindingVariableName(String) + */ + public ScriptItemProcessorBuilder itemBindingVariableName(String itemBindingVariableName) { + this.itemBindingVariableName = itemBindingVariableName; + + return this; + } + + /** + * Returns a fully constructed {@link ScriptItemProcessor}. + * + * @return a new {@link ScriptItemProcessor} + */ + public ScriptItemProcessor build() { + if (this.scriptResource == null && !StringUtils.hasText(this.scriptSource)) { + throw new IllegalArgumentException("scriptResource or scriptSource is required."); + } + if (StringUtils.hasText(this.scriptSource)) { + Assert.hasText(this.language, "language is required when using scriptSource."); + } + + ScriptItemProcessor processor = new ScriptItemProcessor<>(); + if (StringUtils.hasText(this.itemBindingVariableName)) { + processor.setItemBindingVariableName(this.itemBindingVariableName); + } + if (this.scriptResource != null) { + processor.setScript(this.scriptResource); + } + if (this.scriptSource != null) { + processor.setScriptSource(this.scriptSource, this.language); + } + return processor; + } +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/ScriptItemProcessorTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/ScriptItemProcessorTests.java index 5279827ac..165456b2c 100644 --- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/ScriptItemProcessorTests.java +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/ScriptItemProcessorTests.java @@ -150,6 +150,17 @@ public class ScriptItemProcessorTests { assertEquals("Incorrect transformed value", "SS", scriptItemProcessor.process("ss")); } + @Test + public void testItemBinding() throws Exception { + ScriptItemProcessor scriptItemProcessor = new ScriptItemProcessor(); + scriptItemProcessor.setScriptSource("foo.contains('World');", "javascript"); + scriptItemProcessor.setItemBindingVariableName("foo"); + + scriptItemProcessor.afterPropertiesSet(); + + assertEquals("Incorrect transformed value", true, scriptItemProcessor.process("Hello World")); + } + @Test(expected = IllegalStateException.class) public void testNoScriptSet() throws Exception { ScriptItemProcessor scriptItemProcessor = new ScriptItemProcessor(); diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/builder/ScriptItemProcessorBuilderTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/builder/ScriptItemProcessorBuilderTests.java new file mode 100644 index 000000000..7eb28ca33 --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/item/support/builder/ScriptItemProcessorBuilderTests.java @@ -0,0 +1,109 @@ +/* + * Copyright 2017 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.batch.item.support.builder; + +import java.util.ArrayList; +import java.util.List; + +import javax.script.ScriptEngineFactory; +import javax.script.ScriptEngineManager; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.springframework.batch.item.support.ScriptItemProcessor; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +/** + * @author Glenn Renfro + */ +public class ScriptItemProcessorBuilderTests { + private static List availableLanguages = new ArrayList<>(); + + @BeforeClass + public static void populateAvailableEngines() { + List scriptEngineFactories = new ScriptEngineManager().getEngineFactories(); + + for (ScriptEngineFactory scriptEngineFactory : scriptEngineFactories) { + availableLanguages.addAll(scriptEngineFactory.getNames()); + } + } + + @Before + public void setup() { + assumeTrue(availableLanguages.contains("javascript")); + } + + @Test + public void testScriptSource() throws Exception { + ScriptItemProcessor scriptItemProcessor = new ScriptItemProcessorBuilder() + .scriptSource("item.toUpperCase();").language("javascript").build(); + scriptItemProcessor.afterPropertiesSet(); + + assertEquals("Incorrect transformed value", "AA", scriptItemProcessor.process("aa")); + } + + @Test + public void testItemBinding() throws Exception { + ScriptItemProcessor scriptItemProcessor = new ScriptItemProcessorBuilder() + .scriptSource("foo.contains('World');").language("javascript").itemBindingVariableName("foo").build(); + scriptItemProcessor.afterPropertiesSet(); + + assertEquals("Incorrect transformed value", true, scriptItemProcessor.process("Hello World")); + } + + @Test + public void testScriptResource() throws Exception { + Resource resource = new ClassPathResource("org/springframework/batch/item/support/processor-test-simple.js"); + ScriptItemProcessor scriptItemProcessor = new ScriptItemProcessorBuilder() + .scriptResource(resource).build(); + scriptItemProcessor.afterPropertiesSet(); + + assertEquals("Incorrect transformed value", "BB", scriptItemProcessor.process("bb")); + } + + @Test + public void testNoScriptSourceNorResource() throws Exception { + validateExceptionMessage(new ScriptItemProcessorBuilder<>(), + "scriptResource or scriptSource is required."); + } + + @Test + public void testNoScriptSourceLanguage() throws Exception { + validateExceptionMessage(new ScriptItemProcessorBuilder().scriptSource("foo.contains('World');"), + "language is required when using scriptSource."); + + } + + private void validateExceptionMessage(ScriptItemProcessorBuilder 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()); + } + } + +}