2

I am using Spring Boot v2.6.2 and Spring Batch and developing a classifier-composite-item-processor-job example. Below is the error I'm getting.

Error -

java.lang.IllegalStateException: No matching engine found for file extension 'js'
    at org.springframework.scripting.support.StandardScriptEvaluator.getScriptEngine(StandardScriptEvaluator.java:185) ~[spring-context-5.3.14.jar:5.3.14]
    at org.springframework.scripting.support.StandardScriptEvaluator.evaluate(StandardScriptEvaluator.java:143) ~[spring-context-5.3.14.jar:5.3.14]
    at org.springframework.batch.item.support.ScriptItemProcessor.process(ScriptItemProcessor.java:64) ~[spring-batch-infrastructure-4.3.4.jar:4.3.4]
    at org.springframework.batch.item.support.ScriptItemProcessor$$FastClassBySpringCGLIB$$b97e2b15.invoke(<generated>) ~[spring-batch-infrastructure-4.3.4.jar:4.3.4]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:783) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.doProceed(DelegatingIntroductionInterceptor.java:137) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.support.DelegatingIntroductionInterceptor.invoke(DelegatingIntroductionInterceptor.java:124) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:753) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:698) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.batch.item.support.ScriptItemProcessor$$EnhancerBySpringCGLIB$$acbdf7e6.process(<generated>) ~[spring-batch-infrastructure-4.3.4.jar:4.3.4]
    at org.springframework.batch.item.support.ClassifierCompositeItemProcessor.processItem(ClassifierCompositeItemProcessor.java:63) ~[spring-batch-infrastructure-4.3.4.jar:4.3.4]
    at org.springframework.batch.item.support.ClassifierCompositeItemProcessor.process(ClassifierCompositeItemProcessor.java:54) ~[spring-batch-infrastructure-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.step.item.SimpleChunkProcessor.doProcess(SimpleChunkProcessor.java:134) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.step.item.SimpleChunkProcessor.transform(SimpleChunkProcessor.java:319) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.step.item.SimpleChunkProcessor.process(SimpleChunkProcessor.java:210) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.step.item.ChunkOrientedTasklet.execute(ChunkOrientedTasklet.java:77) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:407) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback.doInTransaction(TaskletStep.java:331) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:140) ~[spring-tx-5.3.14.jar:5.3.14]
    at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:273) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:82) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:375) ~[spring-batch-infrastructure-4.3.4.jar:4.3.4]
    at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215) ~[spring-batch-infrastructure-4.3.4.jar:4.3.4]
    at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:145) ~[spring-batch-infrastructure-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:258) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:208) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:152) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.job.AbstractJob.handleStep(AbstractJob.java:413) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.job.SimpleJob.doExecute(SimpleJob.java:136) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:320) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:149) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50) ~[spring-core-5.3.14.jar:5.3.14]
    at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:140) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:344) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:128) ~[spring-batch-core-4.3.4.jar:4.3.4]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.14.jar:5.3.14]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.14.jar:5.3.14]
    at com.sun.proxy.$Proxy56.run(Unknown Source) ~[na:na]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.execute(JobLauncherApplicationRunner.java:199) ~[spring-boot-autoconfigure-2.6.2.jar:2.6.2]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.executeLocalJobs(JobLauncherApplicationRunner.java:173) ~[spring-boot-autoconfigure-2.6.2.jar:2.6.2]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.launchJobFromProperties(JobLauncherApplicationRunner.java:160) ~[spring-boot-autoconfigure-2.6.2.jar:2.6.2]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:155) ~[spring-boot-autoconfigure-2.6.2.jar:2.6.2]
    at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.run(JobLauncherApplicationRunner.java:150) ~[spring-boot-autoconfigure-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:758) ~[spring-boot-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:748) ~[spring-boot-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:309) ~[spring-boot-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-2.6.2.jar:2.6.2]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1290) ~[spring-boot-2.6.2.jar:2.6.2]
    at com.example.classifiercompositeitemprocessorjob.ClassifierCompositeItemProcessorJobApplication.main(ClassifierCompositeItemProcessorJobApplication.java:100) ~[classes/:na]

MainApp.java

package com.example.classifiercompositeitemprocessorjob;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.adapter.ItemProcessorAdapter;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.builder.FlatFileItemReaderBuilder;
import org.springframework.batch.item.support.ClassifierCompositeItemProcessor;
import org.springframework.batch.item.support.ScriptItemProcessor;
import org.springframework.batch.item.support.builder.ClassifierCompositeItemProcessorBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.classify.Classifier;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;

@SpringBootApplication
@EnableBatchProcessing
public class ClassifierCompositeItemProcessorJobApplication {
    @Autowired
    public JobBuilderFactory jobBuilderFactory;

    @Autowired
    public StepBuilderFactory stepBuilderFactory;

    @Bean
    @StepScope
    public FlatFileItemReader<Customer> customerItemReader(@Value("#{jobParameters['customerFile']}") Resource inputFile) {

        return new FlatFileItemReaderBuilder<Customer>()
                .name("customerItemReader")
                .delimited()
                .names("firstName", "middleInitial", "lastName", "address", "city", "state", "zip")
                .targetType(Customer.class)
                .resource(inputFile)
                .build();
    }

    @Bean
    public ItemWriter<Customer> itemWriter() {
        return (items) -> items.forEach(System.out::println);
    }

    @Bean
    public ItemProcessorAdapter<Customer, Customer> upperCaseItemProcessor(UpperCaseNameService service) {
        ItemProcessorAdapter<Customer, Customer> adapter = new ItemProcessorAdapter<>();
        adapter.setTargetObject(service);
        adapter.setTargetMethod("upperCase");
        return adapter;
    }

    @Bean
    @StepScope
    public ScriptItemProcessor<Customer, Customer> lowerCaseItemProcessor(@Value("#{jobParameters['script']}") Resource script) {
        ScriptItemProcessor<Customer, Customer> itemProcessor = new ScriptItemProcessor<>();
        itemProcessor.setScript(script);
        return itemProcessor;
    }

    @Bean
    public Classifier classifier() {
        return new ZipCodeClassifier(upperCaseItemProcessor(null), lowerCaseItemProcessor(null));
    }

    @Bean
    public ClassifierCompositeItemProcessor<Customer, Customer> itemProcessor() {
        return new ClassifierCompositeItemProcessorBuilder<Customer, Customer>()
                .classifier(classifier())
                .build();
    }

    @Bean
    public Step copyFileStep() {
        return this.stepBuilderFactory.get("copyFileStep")
                .<Customer, Customer>chunk(50)
                .reader(customerItemReader(null))
                .processor(itemProcessor())
                .writer(itemWriter())
                .build();
    }

    @Bean
    public Job job() throws Exception {
        return this.jobBuilderFactory.get("job")
                .incrementer(new RunIdIncrementer())
                .start(copyFileStep())
                .build();
    }

    public static void main(String[] args) {

        SpringApplication.run(ClassifierCompositeItemProcessorJobApplication.class,
                "customerFile=/input/customer.csv", "script=/input/lowerCase.js");
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>classifier-composite-item-processor-job</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>classifier-composite-item-processor-job</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-batch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.batch</groupId>
            <artifactId>spring-batch-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

enter image description here

xerx593
  • 12,237
  • 5
  • 33
  • 64
PAA
  • 1
  • 46
  • 174
  • 282
  • Does this answer your question? [Spring Batch ScriptItemProcessor](https://stackoverflow.com/questions/66818971/spring-batch-scriptitemprocessor) – xerx593 Jan 02 '22 at 13:57
  • ...actually the "duplicate" is about python, but the accepted answer also applies to your problem (spring "knows" only groovy, bash and "standard", spring integration can (additionally?) do "Ruby, JRuby, Groovy and Kotlin" ([link](https://docs.spring.io/spring-integration/reference/html/scripting.html#scripting)), but regarding "js" i (quickly) found (only) [this(mmmhh)](https://www.openhab.org/docs/configuration/jsr223.html) and [this(aaahhh)](https://github.com/scijava/scripting-javascript). – xerx593 Jan 02 '22 at 14:07
  • Haha! [look at this](https://stackoverflow.com/questions/tagged/sping-batch%2bjavascript): No *single question* on this site here tagged [tag:spring-batch] AND [tag:javascript]! May I edit/add it? – xerx593 Jan 02 '22 at 14:13
  • Yes please add it, reading all links, if you know quick code snippet, please paste it – PAA Jan 02 '22 at 14:16
  • forget my first links (except spring integration), ["nashorn"](https://docs.oracle.com/javase/8/docs/technotes/guides/scripting/prog_guide/api.html) (built-in) ... it should be ...or [apache sling](https://sling.apache.org/documentation/bundles/scripting.html) ... but for a (good) answer I'd like to test... ;( – xerx593 Jan 02 '22 at 14:29

1 Answers1

0

Adding (nowadays) one of:

  • nashorn (How to use Nashorn in Java 15 and later?) :
    <dependency>
       <groupId>org.openjdk.nashorn</groupId>
       <artifactId>nashorn-core</artifactId>
       <version>15.3</version> <!-- latest -->
    </dependency>
    
  • or graal-js (https://golb.hplar.ch/2020/04/java-javascript-engine.html):
    <dependency>
       <groupId>org.graalvm.js</groupId>
       <artifactId>js</artifactId>
       <version>21.3.0</version> <!-- latest -->
    </dependency>
    <dependency>
       <groupId>org.graalvm.js</groupId>
       <artifactId>js-scriptengine</artifactId>
       <version>21.3.0</version> <!-- latest -->
    </dependency>
    

..will resolve our:

No matching engine found for file extension 'js'

But then it's all up to the js!

Further(&deeper) reads:


With the following test data:

John;F;Jackson;123 1st Ave.;New York;NY;10003
Jack;C;Johnson;456 E 2nd St;Los Angeles;CA;90012

this script (How to interact with spring-batch-items!??):

print("Hello, " + item);

What I could get out of your config, was:

...
2022-01-02 18:19:25.063  INFO 13164 --- [           main] o.s.batch.core.job.SimpleStepHandler     : Executing step: [copyFileStep]
Hello, Customer(firstName=Jack, middleInitial=C, lastName=Johnson, address=456 E 2nd St, city=Los Angeles, state=CA, zip=90012)
Customer(firstName=JOHN, middleInitial=F, lastName=JACKSON, address=123 1ST AVE., city=NEW YORK, state=NY, zip=10003)
2022-01-02 18:19:25.390  INFO 13164 --- [           main] o.s.batch.core.step.AbstractStep         : Step: [copyFileStep] executed in 327ms
2022-01-02 18:19:25.394  INFO 13164 --- [           main] o.s.b.c.l.support.SimpleJobLauncher      : Job: [SimpleJob: [name=job]] completed with the following parameters: [{run.id=1, customerFile=/input/customer.csv, script=/input/lowerCase.js}] and the following status: [COMPLETED] in 339ms
2022-01-02 18:19:25.397  INFO 13164 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...
2022-01-02 18:19:25.399  INFO 13164 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.
------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------
Total time:  3.899 s
Finished at: 2022-01-02T18:19:25+01:00
------------------------------------------------------------------------

(graal with more warnings!:)... classifier was like:

  @Override
  public ItemProcessor<Customer, Customer> classify(Customer classifiable) {
    if (classifiable != null) {
      if (Integer.valueOf(classifiable.getZip()) <= 50000) {
        return upper;
      } else {
        return lower;
      }
    }
    return null;
  }
xerx593
  • 12,237
  • 5
  • 33
  • 64
  • with `print("Hello, " + item);` (as js) we get more lucky... tricky part: what to return ??? :) – xerx593 Jan 02 '22 at 18:13